October 31, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Dumping an Object's State with a DynamicMethod

  • November 7, 2008
  • By Paul Kimmel
  • Send Email »
  • More Articles »

Introduction

In two prior articles, I talked about the general benefit of using Reflection. With Reflection, you can write a general solution, such as dumping an object's state, one time and it works for any object. The problem is speed. After that, I talked about writing a general solution that emits a specific solution. The benefit of Relection.Emit is speed. The problem with emitting a dynamic assembly is that it lingers around until your application shuts down. This could end up hogging needed resources. A best of class solution is to write a general solution that emits a specific typed-based solution based on MSIL but uses a DynamicMethod instead.

The new and improved DynamicMethod means that you can emit performant, dynamic MSIL but when the DynamicMethod goes out of scope so goes the emitted code. No more slow, generic Reflection code, and no more assemblies hanging around stinking up your memory.

Writing an Emitter Using a DynamicMethod

When you write an emitter that generates a dynamic assembly, you need an AssemblyName, AppDomain, AssemblyBuilder, and a TypeBuilder. After the TypedBuilder, you add behaviors to the type with a MethodBuilder. Using the DynamicMethod introduced in .NET 2.0, you start right off with the MethodBuilder. In the MethodBuilder, you add the same kind of code to generate the same kind of MSIL that you'd add to a dynamic assembly. The difference is that the DynamicMethod is designed to be unloadable after you are done using it even if your application is still running.

The code in Listing 1 creates a DynamicMethod. The DynamicMethod will contain emitted code that determines the type of an argument object and emits code that dumps the state of that object. The code is JIT (Just-In-Time) compiled; the result is that the first time a DynamicMethod is called, it is slow like plain old Reflection because you are paying for JIT compiling. Every subsequent time, the performance of the dynamic code is identical to literal hard code that performs the same task. Table 1 shows you comparisons among different kinds of implementations.

Listing 1: A DynamicMethod that emits code to dump a collection of object's and their state.

Imports System.Reflection.Emit
Imports System.Reflection
Imports System.IO

Module DynamicMethodCreator

   Public Function CreateDumpDelegate(Of T)(ByVal obj _
      As IEnumerable(Of T)) As DynamicMethod

      Dim method As DynamicMethod = New DynamicMethod( _
         "Dump" + GetType(T).Name, Nothing, _
         New Type() {GetType(IEnumerable(Of T)), _
         GetType(TextWriter)})

      Dim write As MethodInfo = _
         GetType(Console).GetMethod("WriteLine", _
         New Type() {GetType(String)})


      Dim concat As MethodInfo = _
         GetType(System.String).GetMethod("Concat", _
         New Type() {GetType(String), GetType(String)})

      Dim MoveNext As MethodInfo = _
         GetType(System.Collections.IEnumerator). _
         GetMethod("MoveNext", New Type() {})

      Dim enumer As MethodInfo = _
         GetType(IEnumerable(Of T)).GetMethod("GetEnumerator", _
         New Type() {})

      Dim get_Current As MethodInfo = _
         GetType(System.Collections.IEnumerator). _
         GetMethod("get_Current")
         ' get the properties and use them to build writeline
         ' statements
      Dim properties() As PropertyInfo = GetType(T).GetProperties()
      Dim generator As ILGenerator = method.GetILGenerator()

      Dim localT As LocalBuilder = _
         generator.DeclareLocal(GetType(T))
      Dim localI As LocalBuilder = _
         generator.DeclareLocal(GetType(IEnumerator(Of T)))
      Dim localB As LocalBuilder = _
         generator.DeclareLocal(GetType(Boolean))
      generator.Emit(OpCodes.Nop)
      generator.Emit(OpCodes.Nop)
      ' Try
      generator.BeginExceptionBlock()
      generator.Emit(OpCodes.Ldarg_0)
      generator.EmitCall(OpCodes.Callvirt, enumer, Nothing)
      generator.Emit(OpCodes.Stloc_1)
      Dim IL_000b As Label = generator.DefineLabel()
      Dim IL_003f As Label = generator.DefineLabel()
      generator.Emit(OpCodes.Br, IL_003f)    ' BR_S is wrong
      generator.MarkLabel(IL_000b)
      generator.Emit(OpCodes.Ldloc_1)
      generator.EmitCall(OpCodes.Callvirt, get_Current, Nothing)
      ' Added this to convert object to type T
      generator.Emit(OpCodes.Castclass, GetType(T))
      generator.Emit(OpCodes.Stloc_0)
      ' write each property
      For Each prop As PropertyInfo In properties
         generator.Emit(OpCodes.Ldstr, prop.Name + "=")
         generator.Emit(OpCodes.Ldloc_0)
         Dim get_Prop As MethodInfo = GetType(T).GetMethod("get_" _
            + prop.Name, New Type() {})
         generator.EmitCall(OpCodes.Callvirt, get_Prop, Nothing)

         If (prop.PropertyType.IsValueType) Then
            generator.Emit(OpCodes.Box, prop.PropertyType)
            generator.Emit(OpCodes.Callvirt, _
               GetType(Object).GetMethod("ToString", New Type() {}))
         End If


         generator.Emit(OpCodes.Call, concat)
         generator.Emit(OpCodes.Call, write)
         generator.Emit(OpCodes.Nop)

      Next

      generator.Emit(OpCodes.Nop)
      generator.MarkLabel(IL_003f)
      generator.Emit(OpCodes.Ldloc_1)
      generator.EmitCall(OpCodes.Callvirt, MoveNext, Nothing)
      generator.Emit(OpCodes.Stloc_2)
      generator.Emit(OpCodes.Ldloc_2)
      ' Brtrue_S is wrong
      generator.Emit(OpCodes.Brtrue, IL_000b)
      generator.Emit(OpCodes.Nop)
      Dim IL_0060 As Label = generator.DefineLabel()
      generator.Emit(OpCodes.Leave_S, IL_0060)
      ' begin finally
      generator.BeginFinallyBlock()
      generator.Emit(OpCodes.Ldloc_1)
      generator.Emit(OpCodes.Ldnull)
      generator.Emit(OpCodes.Ceq)
      generator.Emit(OpCodes.Ldc_I4_0)
      generator.Emit(OpCodes.Ceq)
      generator.Emit(OpCodes.Stloc_2)
      generator.Emit(OpCodes.Ldloc_2)
      Dim IL_005e As Label = generator.DefineLabel
      generator.Emit(OpCodes.Brfalse_S, IL_005e)
      generator.Emit(OpCodes.Ldloc_1)
      Dim dispose As MethodInfo = _
         GetType(System.IDisposable).GetMethod("Dispose", New Type() {})
      generator.EmitCall(OpCodes.Callvirt, dispose, Nothing)
      generator.Emit(OpCodes.Nop)
      generator.MarkLabel(IL_005e)
      generator.Emit(OpCodes.Nop)
      generator.EndExceptionBlock()
      generator.MarkLabel(IL_0060)
      generator.Emit(OpCodes.Nop)
      generator.Emit(OpCodes.Ret)
      Return method

   End Function
End Module




Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel