Using Reflection Emit

Reflection emit is the process of creating your own types and code at runtime. When you use reflection emit, you create not only a type, but also the members of that typeand compile them on the fly, "emitting" the MSIL code for the new type. Note that this is an advanced technique, and the code here is going to get a little involved. Not many programmers use reflection emit.

To get an idea of the kinds of problems reflection emit can solve, take a look at ch14_08.cs, Listing 14.8. In that example, we'll use a method named CountLoop to count from 1 to 30:

 
 public int CountLoop(int n) {   int total = 0;   for(int loopIndex = 1; loopIndex <= n; loopIndex++)   {     total++;   }   return total; } 

This method relies on a loop to execute, which is fairly fast, but it can't beat a custom method like this, which does the same thing as CountLoop , but uses addition:

 
 public int CountSimple() {   return 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +     1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +     1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1; } 

This second method, named CountSimple , just counts to 30 by adding one 30 times. We'll keep track of how fast it takes CountLoop and CountSimple to count to 30 using a TimeSpan object, as you see in Listing 14.8. Note that because computers don't take long to count to 30, we'll count to 30 10,000,000 times to get a time we can actually measure.

Listing 14.8 Comparing Counting Methods (ch14_08.cs)
 using System; using System.Diagnostics; public class Counter {   public int CountLoop(int totalCount)   {     int total = 0;     for(int loopIndex = 1; loopIndex <= totalCount; loopIndex++)     {       total++;     }     return total;   }   public int CountSimple()   {     return 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +       1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +       1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1;   } } public class ch14_08 {   public static void Main()   {  Counter counter = new Counter();   int total = 0;   DateTime begin = DateTime.Now;   for (int loopIndex = 0; loopIndex < 10000000; loopIndex++)   {   total = counter.CountLoop(30);   }   TimeSpan duration = DateTime.Now - begin;   System.Console.WriteLine("Looped to {0} 10,000,000 times in {1} milliseconds.",   total, duration.TotalMilliseconds);   begin = DateTime.Now;   for (int loopIndex = 0; loopIndex < 10000000; loopIndex++)   {   total = counter.CountSimple();   }   duration = DateTime.Now - begin;   System.Console.WriteLine("Counted to {0} 10,000,000 times in {1} milliseconds.",   total, duration.TotalMilliseconds);  } } 

Here are the results. As you can see, the custom method is far faster than using a loop:

 
 C:\>ch14_07 Looped to 30 10,000,000 times in 901.296 milliseconds. Counted to 30 10,000,000 times in 40.0576 milliseconds. 

The whole idea behind reflection emit is to be able to create custom methods like CountSimple on the fly. It's not an easy thing to do, because you're responsible for doing a lot of workincluding creating the MSIL for the custom methodbut it can be done.

This new example will be called ch14_09.cs. For the sake of reference, we'll include the Counter class we just created and its CountLoop method in this example so we can see how much faster the method we create on the fly is. In this example, we use reflection emit to create the other method, CountSimple , from scratch.

Like the previous version of CountSimple , we're going to count to 30 by adding 1 + 1 + 1 .. + 1, but we're going to write this new version of CountSimple at runtime. We'll do this by creating a new class, Reflector , and then calling Reflector.CountSimple(30) to count to 30.

The Reflector class's CountSimple method will call a new method we'll write, Emitter , which will return an AssemblyBuilder object that lets you create assemblies. These assemblies will have a built-in method named GeneratedCountSimple , which will be our custom counting method. Because we're generating a new assembly on the fly, C# will have no idea which parameters that assembly's GeneratedSimple method takes and what its return type is. To give the compiler this information, we'll create an interface, ICounter , with one method, GeneratedSimple :

 
 public interface ICounter {   int GeneratedCountSimple(); } 

All we have to do in our new version of CountSimple is use the AssemblyBuilder object's CreateInstance method to create a new assembly, store that assembly in an ICounter object, call its GeneratedCountSimple method, and return the result that method gives us:

 
 public class Reflector {   ICounter counter = null;   public int CountSimple(int totalCount)   {     if (counter == null)     {       AssemblyBuilder assemblyBuilder = Emitter(totalCount);       counter = (ICounter) assemblyBuilder.CreateInstance("Counter");     }     return (counter.GeneratedCountSimple());   }   .   .   . 

That's it for CountSimple . The only question now is how to create the Emitter method, which returns the AssemblyBuilder object that lets us create assemblies supporting the GeneratedCountSimple method. To create a new AssemblyBuilder , we start by giving the assemblies we'll create a name , CounterAssembly , using an AssemblyName object. Next, we create the AssemblyBuilder object we'll return from this method, using the current application domain's DefineDynamicAssembly method. You can get the current application domain using the static Thread.GetDomain method (more on threads and how to use the Thread class in Chapter 15, "Using Multithreading and Remoting"):

 
 AssemblyBuilder Emitter(int totalCount) {   AssemblyName assemblyName = new AssemblyName();   assemblyName.Name = "CounterAssembly";   AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(     assemblyName, AssemblyBuilderAccess.Run); 

To structure the assemblies built by this assembly builder, we create a ModuleBuilder and TypeBuilder object that will create a new type named Counter :

 
 ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("Module1"); TypeBuilder typeBuilder =   moduleBuilder.DefineType("Counter", TypeAttributes.Public); typeBuilder.AddInterfaceImplementation(typeof(ICounter)); 

Now we will create the GeneratedCountSimple method in the new Counter type. To create this method, we will create a MethodBuilder object, generatedMethod , using the TypeBuilder class's DefineMethod method. Note that we have to specify the parameter types and return type of this new method like this:

 
 Type[] paramTypes = new Type[0]; Type returnType = typeof(System.Int32); MethodBuilder generatedMethod =   typeBuilder.DefineMethod("GeneratedCountSimple",     MethodAttributes.Virtual      MethodAttributes.Public, returnType, paramTypes); 

Now it's time to generate the actual MSIL code for the new GeneratedCountSimple method. You can do that with an object of the ILGenerator class, and in this case, we'll just generate the MSIL to keep adding 1 to a running sum as many times as we're supposed to in this new, generated method. Here's where we use the actual Emit method:

 
 ILGenerator ilGenerator = generatedMethod.GetILGenerator(); ilGenerator.Emit(OpCodes.Ldc_I4, 0); for (int loopIndex = 1; loopIndex <= totalCount; loopIndex++) {   ilGenerator.Emit(OpCodes.Ldc_I4, 1);   ilGenerator.Emit(OpCodes.Add); } ilGenerator.Emit(OpCodes.Ret); 

Our newly generated method, stored in generatedMethod , is simply made up of anonymous MSIL. To give it a name and the coding characteristics we'll need, we'll use the TypeBuilder class's DefineMethodOverride method to override this anonymous method using the ICounter interface's GeneratedCountSimple method. After that, our AssemblyBuilder object is complete, and we return it from the Emitter method like this at the end of the Emitter method:

 
 MethodInfo methodInfo = typeof(ICounter).GetMethod("GeneratedCountSimple");   typeBuilder.DefineMethodOverride(generatedMethod, methodInfo);   typeBuilder.CreateType();   return assemblyBuilder; } 

That's itthe Emitter method returns a new AssemblyBuilder object, and calling that object's CreateInstance will create an assembly with the customized GeneratedCountSimple method that will do our counting in the simplest way. All that remains is to put this new customized method to work and to compare it to the looping method, CountLoop . You can see the code that does that in ch14_09.cs, Listing 14.9.

Listing 14.9 Using Reflection Emit (ch14_09.cs)
 using System; using System.Threading; using System.Reflection; using System.Reflection.Emit; public class Counter {   public int CountLoop(int totalCount)   {     int total = 0;     for(int loopIndex = 1; loopIndex <= totalCount; loopIndex++)     {       total++;     }     return total;   } } public class Reflector {   ICounter counter = null;   public int CountSimple(int totalCount)   {     if (counter == null)     {       AssemblyBuilder assemblyBuilder = Emitter(totalCount);       counter = (ICounter) assemblyBuilder.CreateInstance("Counter");     }     return (counter.GeneratedCountSimple());   }   AssemblyBuilder Emitter(int totalCount)   {     AssemblyName assemblyName = new AssemblyName();     assemblyName.Name = "CounterAssembly";     AssemblyBuilder assemblyBuilder =       Thread.GetDomain().DefineDynamicAssembly(       assemblyName, AssemblyBuilderAccess.Run);     ModuleBuilder moduleBuilder =       assemblyBuilder.DefineDynamicModule("Module1");     TypeBuilder typeBuilder =       moduleBuilder.DefineType("Counter", TypeAttributes.Public);     typeBuilder.AddInterfaceImplementation(typeof(ICounter));     Type[] paramTypes = new Type[0];     Type returnType = typeof(System.Int32);     MethodBuilder generatedMethod =       typeBuilder.DefineMethod("GeneratedCountSimple",       MethodAttributes.Virtual        MethodAttributes.Public, returnType, paramTypes);     ILGenerator ilGenerator = generatedMethod.GetILGenerator();     ilGenerator.Emit(OpCodes.Ldc_I4, 0);     for (int loopIndex = 1; loopIndex <= totalCount; loopIndex++)     {       ilGenerator.Emit(OpCodes.Ldc_I4, 1);       ilGenerator.Emit(OpCodes.Add);     }     ilGenerator.Emit(OpCodes.Ret);     MethodInfo methodInfo =       typeof(ICounter).GetMethod("GeneratedCountSimple");     typeBuilder.DefineMethodOverride(generatedMethod, methodInfo);     typeBuilder.CreateType();     return assemblyBuilder;   } } public interface ICounter {   int GeneratedCountSimple(); } public class ch14_09 {   public static void Main()   {     int total = 0;     Counter counter = new Counter();     DateTime begin = DateTime.Now;     for (int loopIndex = 0; loopIndex < 10000000; loopIndex++)     {       total = counter.CountLoop(30);     }     TimeSpan duration = DateTime.Now - begin;     System.Console.WriteLine(       "Looped to {0} 10,000,000 times in {1} milliseconds.",       total, duration.TotalMilliseconds);  Reflector reflector = new Reflector();   begin = DateTime.Now;   for (int loopIndex = 0; loopIndex < 10000000; loopIndex++)   {   total = reflector.CountSimple(30);   }   duration = DateTime.Now - begin;   System.Console.WriteLine(   "Counted to {0} 10,000,000 times in {1} milliseconds.",   total, duration.TotalMilliseconds);  } } 

Here are the results you see when you run ch14_09. As you can see, the custom method we wrote on the fly using reflection emit is indeed much faster than the looping method:

 
 C:\>ch14_09 Looped to 30 10,000,000 times in 871.2528 milliseconds. Counted to 30 10,000,000 times in 280.4032 milliseconds. 

A LITTLE ABOUT SPEED

Why is the custom reflection emit CountSimple method here slower than the previous CountSimple method, which used the code return 1 + 1 .. + 1 ? If you're familiar with the way compilers optimize, you know that the compiler simply added the long 1 + 1 .. + 1 expression in that code to 30 when it compiled that code (as you can verify with ILDASM), so no counting was actually performed in the previous CountSimple method. Actually, the MSIL code we created to increment a value thirty times is about as fast as you can go in .NET programming.


And that's itwe've created a custom method on the fly using reflection emit, creating the method's actual MSIL, and then called that method. An advanced technique to be sure, and not for everyonebut very impressive.



Microsoft Visual C#. NET 2003 Kick Start
Microsoft Visual C#.NET 2003 Kick Start
ISBN: 0672325470
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net