22.4. Generating Dynamic IL Using Reflection Emit

In Java it is possible to generate source code dynamically and compile it to byte code. In fact, the JSP technology is based on just that principle, in which the JSP is compiled at runtime to a servlet class, which is then run by the JVM.

In C#, too, you can generate the code and compile it at runtime. However, C# goes a step further and provides an API for actually dealing with the generated IL, a capability known as reflection emit. This would be equivalent to a Java API that manipulates byte code. The classes in the C# System.Reflection.Emit namespace can be used to programmatically generate IL instructions.

To better understand what we mean by this, let's look at a simple example. Listing 22.8 is a simple class that basically generates the number 3 on the system console when run. Here we actually wrote the SimpleClass class in our IDE, compiled it, and ran it.

Listing 22.8. A Simple Class (C#)
using System;

public interface ISimple {
  int GetInt();
}

public class SimpleClass : ISimple {
  public int GetInt() {
    return 3;
  }
  public static void Main(string[] args) {
    ISimple sc = new SimpleClass();
    Console.WriteLine(sc.GetInt());
  }
}

It is possible to achieve the same result as that of Listing 22.8 by defining SimpleClass programmatically instead of writing it. Listing 22.9 shows the code for doing that.

Listing 22.9. Programmatic Generation of SimpleClass Using Reflection Emit (C#)
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Reflection.Emit;

public class Driver {
  public interface ISimple {
    int GetInt();
  }

  public static void Main(string[] args) {

    AssemblyName assemblyName = new AssemblyName( );
    assemblyName.Name = "GeneralAssembly";
    AssemblyBuilder newAssembly =
      Thread.GetDomain().DefineDynamicAssembly(
      assemblyName, AssemblyBuilderAccess.Run);
    ModuleBuilder newModule =
      newAssembly.DefineDynamicModule("Simple");

    TypeBuilder myType =
      newModule.DefineType(
      "SimpleClass", TypeAttributes.Public);

    myType.AddInterfaceImplementation(
      typeof(ISimple));

    Type[] paramTypes = new Type[0];
    Type returnType = typeof(int);
    MethodBuilder simpleMethod =
      myType.DefineMethod(
      "GetInt",
      MethodAttributes.Public |
      MethodAttributes.Virtual,
      returnType,
      paramTypes);
    ILGenerator generator =
      simpleMethod.GetILGenerator( );

    generator.Emit(OpCodes.Ldc_I4, 3);
    generator.Emit(OpCodes.Ret);

    MethodInfo getIntInfo =
      typeof(ISimple).GetMethod("GetInt");

    myType.DefineMethodOverride(simpleMethod, getIntInfo);
    myType.CreateType( );
    ISimple sc =
    (ISimple)newAssembly.CreateInstance("SimpleClass");
    Console.WriteLine(sc.GetInt());
  }
}

The first step in programmatically defining the class is to define the assembly and the module that will house the class:

AssemblyName assemblyName = new AssemblyName( );
assemblyName.Name = "GeneralAssembly";

An AssemblyName is an object that fully describes an assembly's unique identity. An assembly's identity consists of a simple name, a version number, a cryptographic key pair, and a supported culture.

With this object in hand, we can create a new AssemblyBuilder object. To do so, we call DefineDynamicAssembly on the current domain, which we get by calling the static GetDomain( ) method of the Thread object. The parameters to the GetDomain() method are the AssemblyName object we just created and an AssemblyBuilderAccess enumeration value (either Run, RunandSave, or Save). We use Run in this case to indicate that the assembly can be run but cannot be saved:

AssemblyBuilder newAssembly =
Thread.GetDomain().DefineDynamicAssembly(
assemblyName, AssemblyBuilderAccess.Run);

With this newly created AssemblyBuilder object, we are ready to create a ModuleBuilder object. The job of the ModuleBuilder, not surprisingly, is to build a module dynamically:

ModuleBuilder newModule =
newAssembly.DefineDynamicModule("Simple");

Next, we define the class and the interface it implements:

TypeBuilder myType = newModule.DefineType("SimpleClass",
         TypeAttributes.Public);
myType.AddInterfaceImplementation(typeof(ISimple));

Details about the one instance method on the class are then written as follows:

Type[] paramTypes = new Type[0];
Type returnType = typeof(int);
MethodBuilder simpleMethod =
  yType.DefineMethod("GetInt",MethodAttributes.Public |
    MethodAttributes.Virtual,returnType, paramTypes);

We then use the MethodBuilder object we created to get an ILGenerator object:

ILGenerator generator = simpleMethod.GetILGenerator( );

With the ILGenerator object in hand, we are ready to emit the OpCodes. These are the very OpCodes that the C# compiler would have created. First, we emit the value 3 to the stack and then return it. Now we're ready to create a MethodInfo object that will describe the method:

MethodInfo getIntInfo = typeof(ISimple).GetMethod("GetInt");

Now we must specify the implementation that will implement the method. We call DefineMethodOverride on the TypeBuilder object we created earlier, passing the MethodBuilder we created along with the MethodInfo object we just created:

myType.DefineMethodOverride(simpleMethod, getIntInfo);

After the assembly is created, we instantiate an instance of the SimpleClass class and call the method GetInt on it. The output will be the same as that of Listing 22.8.

This example illustrates how you can dynamically define classes and directly interact with the IL of the class using the reflection emit API.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.161.116