Dumping an Object's State with a DynamicMethod

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

Table 1: Comparing algorithms and relative performance

Solution General Use Relative Speed JIT Compile Assembly Unloaded
Hard Code No Fast No N/A
Reflection Yes No No N/A
Reflection Emit Yes Yes Yes No
DynamicMethod Yes Yes Yes Yes

The code in Listing 1 starts by creating a DynamicMethod that will accept an argument of IEnumerable(Of T) and a TextWriter although the TextWriter is not used in the emitted code. Local variables of type MethodInfo are initialized for methods the emitter will need, such as Console.WriteLine, String.Concat, IEnumerator.MoveNext, IEnumerable.GetEnumerator, and IEnumerator.get_Current. Next, you need to request an ILGenerator from the DynamicMethod. An ILGenerator is used to convert OpCodes and things to MSIL.

Next, some local variables are declared. These will contain type information, an IEnumerator(Of T), and a Boolean. The Nop is hard to pin down but Amanda Silver told me that they are used to permit breakpoints on non-executable lines of code.

BeginExceptionBlock is where you start a try..catch..finally block in MSIL. The rest of the code basically contains an enumerator for the passed-in collection. The PropertyInfo is obtained for the type T in the collection and each of the properties and property values are requested and sent to the Console in a loop. The If(prop.PropertyType.IsValueType checks to see if the property type is a value type. If it is the code boxes the property—wraps an obejct around it—so it can call ToString on the value type. This code will execute for things like integers, decimals, basically value types. After each property and value is concatenated and sent to the console, the next object in the collection is obtained from the enumerator and the process repeats. Eventually, the collection will run out of obejcts and cleanup occurs in the finally block. The last step is to return the DynamicMethod so the consuming code can use it.

Dumping an Object's State with a DynamicMethod

Using the DynamicMethod State Dumper

The Main method creates a list of customers. Any sample data or type will do. After the customers collection has been created, the DynamicMethodCreator.CreateDumpDelegate is called by passing in the list of customers. The returned DynamicMethod is used to create a delegate and assign it to the local delegate named MyDelegate.

Finally, MyDelegate is used like a regular old method. The only change you'd need to make if you wanted to dump Orders or Widgets instead of customers would be to initiatize an additional new variable using the new type; instead of mydelegate As DumpDelegate(Of Customer), replace Customer with the new type you'd like to dump.

Listing 2: Sample code to use the DynamicMethod.

Imports System.Reflection
Imports System.IO
Imports System.Runtime.CompilerServices
Imports System.Reflection.Emit


Module Module1


   Sub Main()

      ' Create customer list
      Dim customers As List(Of Customer) = New List(Of Customer)
      For i As Integer = 0 To 50
         customers.Add(New Customer(i.ToString()))
      Next

      Dim method As DynamicMethod = _
         DynamicMethodCreator.CreateDumpDelegate(customers)
      MyDelegate = method.CreateDelegate( _
         GetType(DumpDelegate(Of Customer)))
      MyDelegate(customers, Console.Out)
      Console.ReadLine()

   End Sub


   Private MyDelegate As DumpDelegate(Of Customer)
   Private Delegate Sub DumpDelegate(Of T)( _
      ByVal list As IEnumerable(Of T), ByVal writer As TextWriter)

End Module

If you are asking yourself whether the extra effort is worth the benefit, run some comparisons on the different implementation types in Table 1. The emitted hard-coded example was generally hundreds and in some cases thousands of times faster than the plain old Reflection version. And remember, you only have to write an emitter one time.

Summary

You can hard code a solution over and over, but it's going to cut into your time budget. You can use plain vanilla reflection, but it's going to cut into your performance budget. You can use dynamically emitted assemblies to improve workload and speed things up, but you'll pay in the form of memory. If you write a dynamic emitter that uses a DynamicMethod, you save time, improve performance, and your memory won't suffer. And remember, emitters can be used over and over.

About the Author

Paul Kimmel is a freelance writer for Developer.com and CodeGuru.com. He is the founder of Software Conceptions, Inc, founded in 1990. Paul Kimmel is architect for EDS, an HP Company. You may contact him about article questions at pkimmel@softconcepts.com.

Check out Paul's most recent books, LINQ Unleashed and Teach Yourself the ADO.NET Entity Framework in 24 Hours (coming Spring 2009).

Copyright © 2008 by Paul T. Kimmel. All Rights Reserved.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds