[Previous] [Contents] [Next]

Creating and Executing Code at Run Time

Now that you've seen how to reflect types at run time, late bind to code, and dynamically execute code, let's take the next logical step and create code on the fly. Creating types at run time involves using the System.Reflection.Emit namespace. Using the classes in this namespace, you can define an assembly in memory, create a module for an assembly, define new types for a module (including its members), and even emit the MSIL opcodes for the application's logic.

Although the code in this example is extremely simple, I've separated the server code-a DLL that contains a class creates a method called HelloWorld-from the client code, an application that instantiates the code-generating class and calls its HelloWorld method. (Note that the compiler switches are in the code comments.) An explanation follows for the DLL code, which is here: -

using System;
using System.Reflection;
using System.Reflection.Emit;
namespace ILGenServer
{
    public class CodeGenerator
    {
        public CodeGenerator()
        {
            // Get current currentDomain.
            currentDomain = AppDomain.CurrentDomain;
            // Create assembly in current currentDomain.
            assemblyName = new AssemblyName();
            assemblyName.Name = "TempAssembly";
            assemblyBuilder =
                    currentDomain.DefineDynamicAssembly
                    (assemblyName, AssemblyBuilderAccess.Run);
            // create a module in the assembly
            moduleBuilder = assemblyBuilder.DefineDynamicModule
                                ("TempModule");
            // create a type in the module
            typeBuilder = moduleBuilder.DefineType
                              ("TempClass",
                              TypeAttributes.Public);
            // add a member (a method) to the type
            methodBuilder = typeBuilder.DefineMethod
                                 ("HelloWorld",
                                  MethodAttributes.Public,
                                  null,null);
            // Generate MSIL.
            msil = methodBuilder.GetILGenerator();
            msil.EmitWriteLine("Hello World");
            msil.Emit(OpCodes.Ret);
            // Last "build" step : create type.
            t = typeBuilder.CreateType();
        }
        AppDomain currentDomain;
        AssemblyName assemblyName;
        AssemblyBuilder assemblyBuilder;
        ModuleBuilder moduleBuilder;
        TypeBuilder typeBuilder;
        MethodBuilder methodBuilder;
        ILGenerator msil;
        object o;
        Type t;
        public Type T
        {
            get
            {
                return this.t;
            }
        }
    }
}

First, we instantiate an AppDomain object from the current domain. (You'll see in Chapter 17, "Interoperating with Unmanaged Code," that app domains are functionally similar to Win32 processes.) After that we instantiate an AssemblyName object. The AssemblyName class will be covered in more detail in Chapter 18-the short version is that it's a class used by the assembly cache manager to retrieve information about an assembly. Once we have the current app domain and an initialized assembly name, we call the AppDomain.DefineDynamicAssembly method to create a new assembly. Note that the two arguments that we're passing are an assembly name as well as the mode in which the assembly will be accessed. AssemblyBuilderAccess.Run designates that the assembly can be executed from memory but cannot be saved. The AppDomain.DefineDynamicAssembly method returns an AssemblyBuilder object that we then cast to an Assembly object. At this point, we have a fully functional assembly in memory. Now we need to create its temporary module and that module's type.

We begin by calling the Assembly.DefineDynamicModule method to retrieve a ModuleBuilder object. Once we have the ModuleBuilder object, we call its DefineType method to create a TypeBuilder object, passing to it the name of the type ("TempClass") and the attributes used to define it (TypeAttributes.Public). Now that we have a TypeBuilder object in hand, we can create any type of member that we want. In this case, we create a method by using the TypeBuilder.DefineMethod method.

Finally, we have a brand new type named TempClass with an embedded method called HelloWorld. Now all we do is decide what code to place in this method. To do this, the code instantiates an ILGenerator object by using the MethodBuilder.GetILGenerator method and calls the different ILGenerator methods to write MSIL code into the method.

Note that here we can use standard code such as Console.WriteLine byusing different ILGenerator methods or we can emit MSIL opcodes by using the ILGenerator.Emit method. The ILGenerator.Emit method takes as its only argument an OpCodes class member field that directly relates to an MSIL opcode.

Finally, we call the TypeBuilder.CreateType method. This should always be the last step performed after you've defined the members for a new type. Then we retrieve the Type object for the new type by using the Type.GetType method. This object is stored in a member variable for later retrieval by the client application.

Now all the client has to do is retrieve the CodeGenerator 's Type member, create an Activator instance, instantiate a MethodInfo object from the type, and then invoke the method. Here's the code to do that, with a little error checking added to make sure things work as they should: -

using System;
using System.Reflection;
using ILGenServer;
public class ILGenClientApp
{
    public static void Main()
    {
        Console.WriteLine("Calling DLL function to generate " +
                          "a new type and method in memory...");
        CodeGenerator gen = new CodeGenerator();
        Console.WriteLine("Retrieving dynamically generated type...");
        Type t = gen.T;
        if (null != t)
        {
            Console.WriteLine("Instantiating the new type...");
            object o = Activator.CreateInstance(t);
            Console.WriteLine("Retrieving the type's " +
                              "HelloWorld method...");
            MethodInfo helloWorld = t.GetMethod("HelloWorld");
            if (null != helloWorld)
            {
                Console.WriteLine("Invoking our dynamically " +
                                  "created HelloWorld method...");
                                  helloWorld.Invoke(o, null);
            }
            else
            {
                Console.WriteLine("Could not locate " +
                                  "HelloWorld method");
            }
        }
        else
        {
            Console.WriteLine("Could not access Type from server");
        }
    }
}

Now if you build and execute this application, you will see the following output: -

Calling DLL function to generate a new type and method in memory...
Retrieving dynamically generated type...
Instantiating the new type...
Retrieving the type's HelloWorld method...
Invoking our dynamically created HelloWorld method...
Hello World

Summary

Reflection is the ability to discover type information at run time. The reflection API let's you do such things as iterate an assembly's modules, iterate an assembly's types, and retrieve the different design-time characteristics of a type. Advanced reflection tasks include using reflection to dynamically invoke methods and use types (via late binding) and even to create and execute MSIL code at run time.

[Previous] [Contents] [Next]