Many developers are unfamiliar with this very useful piece of machinery. The DynamicMethod class was introduced with framework version 2.0 and Silverlight version 3 as a way to offer a limited amount of IL generation at runtime without requiring the additional overhead that is needed when generating complete types or assemblies.

The primary use case is for performance optimization of generated or generic code (“generic” as in “general purpose”, not to be confused with the .NET feature of the same name), but there are other valid use cases as well. Typical solutions prior to DynamicMethod involved writing temporary C# source code files to the local temp folder, invoking the cs command-line compiler to create a randomly named assembly, and then loading that compiled assembly into memory. In fact, this is exactly what the XmlSerializer does and is responsible for a lot of the cold startup penalty that is associated with Web Services (Web Services – even WCF services in common configurations – use XmlSerializer and kin under the hood). Yuck!

DynamicMethod was designed to circumvent the worst parts of those scenarios because it allows you to emit IL directly into the body of a new method delegate – without the round-trip to disk and command-line compiler. DynamicMethod objects are also stored on the heap and become eligible for garbage collection (usually a good thing, but not always).

DynamicMethod is also often used to replace code that dynamically invokes reflected members. The reflection is performed once when building the dynamic method, which performs quickly on subsequent calls (the DynamicMethod body consists of MSIL code, which is passed to the CLR’s JIT compiler just like any other pre-compiled module of code). In fact, once the DynamicMethod has been created and JIT’d, the performance is nearly as good as corresponding pre-compiled code, differing mostly because of the slight additional overhead of the delegate invocation needed when calling the method.

To demonstrate, let us contemplate the following simple class which contains a very simple public method:

  1. public class MyClass
  2. {
  3.     public string HelloWorld()
  4.     {
  5.         return "Hello, World!";
  6.     }
  7. }

Let us further contemplate having a reference to an instance of MyClass:

  1. MyClass MyObject = new MyClass();

Pretend that we do not actually have pre-existing knowledge of the MyClass type, but we do know that it has a HelloWorld() method which returns a string, and we wish to invoke that method. This is a classic (admittedly contrived) example where we would be likely to use Reflection.

In fact, our first stab at this might produce code that resembles the following:

  1. BindingFlags Flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod;
  2. string Result = (string)typeof(MyClass).InvokeMember("HelloWorld", Flags, null, MyObject, null);

This will indeed work as expected. And if you only need to call the method once, this technique just might be sufficient for your needs. If so, there is no need to read further. However, let’s presume that for whatever reason we need to call this code more frequently than that. In our next try to improve performance, we might decide to try caching the reflected MethodInfo and reusing this for subsequent calls:

  1. MethodInfo HelloWorldMethod = typeof(MyClass).GetMethod("HelloWorld");
  2. string Result = (string)HelloWorldMethod.Invoke(MyObject, null);

If we hold on to that MethodInfo instance for subsequent calls, then we can save the reflection lookup. This does indeed improve performance by about 40% if you run it a million times (and who doesn’t run HelloWorld a few million times?), which actually isn’t too bad.

But what about using a DynamicMethod? After all, the entire premise of this article is that using DynamicMethod in place of “standard reflection” may improve performance. With anything there is always a tradeoff though. In this case, to use a DynamicMethod means you need to understand some core level of MSIL. Or you at least need to know how to use Reflector to inspect some code you want to model your DynamicMethod on, and set the output language to IL (or you could alternatively use the ISDASM tool). MSIL is rather verbose when compared to the higher-level languages of C#, VB, and others. So it should come as no surprise that we need a few more lines of code to reproduce the same functionality from the previous two samples:

  1. MethodInfo HelloWorldMethod = typeof(MyClass).GetMethod("HelloWorld");
  2. DynamicMethod DM = new DynamicMethod("HelloWorld", typeof(string), new Type[] { typeof(MyClass) });
  3. ILGenerator IL = DM.GetILGenerator();
  4. IL.Emit(OpCodes.Ldarg_0);
  5. IL.Emit(OpCodes.Call, HelloWorldMethod);
  6. IL.Emit(OpCodes.Ret);
  7. Func<MyClass, string> DMDelegate = (Func<MyClass, string>)DM.CreateDelegate(typeof(Func<MyClass, string>));
  8. string Result = DMDelegate(MyObject);

This time we are creating a new DynamicMethod object, emitting a few MSIL opcodes which just take an incoming function parameter (the object we are calling HelloWorld() on), invokes the method and then returns back. Interestingly, we are still using Reflection in order to get a reference to the MethodInfo that we intend to invoke within the DynamicMethod.

So how does the DynamicMethod perform in comparison with the other approaches? Again, with a test run of 1 million iterations, performance improved by 98%. Nice.

Measuring InvokeMember... 1000000 iterations in 1.5643784 seconds.
Measuring MethodInfo.Invoke... 1000000 iterations in 0.8150111 seconds.
Measuring DynamicMethod... 1000000 iterations in 0.0330202 seconds.
Measuring direct call... 1000000 iterations in 0.0136752 seconds.
Press any key to continue.

So even though DynamicMethod requires a little more effort, the performance increase can be very significant in certain scenarios. While this example might seem a bit unrealistic, keep in mind that in most cases where Reflection is used we are invoking far more than a single simple method. In the common use case of custom serializers, we often iterate over large class models, reflecting each property and storing/retrieving values in a very repetitive fashion. You don’t need a million serialization operations to see a performance boost.

For completeness I also ran the same number of iterations by directly calling the HelloWorld() method without any intermediary to measure the overhead of the DynamicMethod delegate call itself. As you can see, this overhead amounts to about half of the cost of invoking the calls via DynamicMethod. Note that this expenditure is a “one-time” cost per call though, and does not scale upwards as the size of the DynamicMethod increases. In other words, this test amplifies the difference about as much as is possible since the method we are calling is doing such little amount of work in the first place.

And just to point out once more (since many folks would assume otherwise) – this capability exists in Silverlight too. It isn’t just a “desktop thing”.

Full source code for the above sample can be found below:

Full Source Code
  1. using System;
  2. using System.Diagnostics;
  3. using System.Reflection;
  4. using System.Reflection.Emit;
  6. namespace ConsoleApplication1
  7. {
  8.     public class Program
  9.     {
  10.         public class MyClass
  11.         {
  12.             public string HelloWorld()
  13.             {
  14.                 return "Hello, World!";
  15.             }
  16.         }
  18.         static void TestInvokeMember(MyClass testObject, int iterations)
  19.         {
  20.             BindingFlags Flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod;
  21.             for (int ix = 0; ix < iterations; ix++)
  22.                 typeof(MyClass).InvokeMember("HelloWorld", Flags, null, testObject, null);
  23.         }
  25.         static void TestMethodInfoInvoke(MyClass testObject, int iterations)
  26.         {
  27.             MethodInfo HelloWorldMethod = typeof(MyClass).GetMethod("HelloWorld");
  28.             for (int ix = 0; ix < iterations; ix++)
  29.                 HelloWorldMethod.Invoke(testObject, null);
  30.         }
  32.         static void TestDynamicMethod(MyClass testObject, int iterations)
  33.         {
  34.             MethodInfo HelloWorldMethod = typeof(MyClass).GetMethod("HelloWorld");
  35.             DynamicMethod DM = new DynamicMethod("HelloWorld", typeof(string), new Type[] { typeof(MyClass) });
  36.             ILGenerator IL = DM.GetILGenerator();
  37.             IL.Emit(OpCodes.Ldarg_0);
  38.             IL.Emit(OpCodes.Call, HelloWorldMethod);
  39.             IL.Emit(OpCodes.Ret);
  40.             Func<MyClass, string> DMDelegate = (Func<MyClass, string>)DM.CreateDelegate(typeof(Func<MyClass, string>));
  41.             for (int ix = 0; ix < iterations; ix++)
  42.                 DMDelegate(testObject);
  43.         }
  45.         static void TestDirectMethod(MyClass testObject, int iterations)
  46.         {
  47.             for (int ix = 0; ix < iterations; ix++)
  48.                 testObject.HelloWorld();
  49.         }
  51.         static void Main(string[] args)
  52.         {
  53.             MyClass MyObject = new MyClass();
  54.             int Iter = 1000000;
  55.             Stopwatch SW = new Stopwatch();
  57.             Console.Write("Measuring InvokeMember… ");
  58.             SW.Reset();
  59.             SW.Start();
  60.             TestInvokeMember(MyObject, Iter);
  61.             SW.Stop();
  62.             Console.WriteLine("{0} iterations in {1} seconds.", Iter, SW.Elapsed.TotalSeconds);
  64.             Console.Write("Measuring MethodInfo.Invoke… ");
  65.             SW.Reset();
  66.             SW.Start();
  67.             TestMethodInfoInvoke(MyObject, Iter);
  68.             SW.Stop();
  69.             Console.WriteLine("{0} iterations in {1} seconds.", Iter, SW.Elapsed.TotalSeconds);
  71.             Console.Write("Measuring DynamicMethod… ");
  72.             SW.Reset();
  73.             SW.Start();
  74.             TestDynamicMethod(MyObject, Iter);
  75.             SW.Stop();
  76.             Console.WriteLine("{0} iterations in {1} seconds.", Iter, SW.Elapsed.TotalSeconds);
  78.             Console.Write("Measuring direct call… ");
  79.             SW.Reset();
  80.             SW.Start();
  81.             TestDirectMethod(MyObject, Iter);
  82.             SW.Stop();
  83.             Console.WriteLine("{0} iterations in {1} seconds.", Iter, SW.Elapsed.TotalSeconds);
  85.             Console.WriteLine("Press any key to continue.");
  86.             Console.ReadKey();
  87.         }
  88.     }
  89. }