Generating Code at Runtime with Reflection.Emit

The System.Reflection.Emit namespace provides objects for generating assemblies, types, and type members at runtime. Basically you need to perform the following operations sequentially:

1. Create an in-memory assembly within the current application domain with an instance of the AssemblyBuilder class.

2. Create a module for containing types via an instance of the ModuleBuilder class.

3. Create types with instances of the TypeBuilder class.

4. Add members to the TypeBuilder via XBuilder objects, such as MethodBuilder, FieldBuilder, and PropertyBuilder.

5. Save the assembly to disk if required.

The code in Listing 47.4 demonstrates how to create dynamically a simple implementation of the Person class with one property and one method.

Listing 47.4 Generating Code at Runtime

image

image

image

image

After you create an AssemblyName for assigning assembly properties and get the instance of the current application domain, you use the AppDomain.DefineDynamicAssembly method to generate an in-memory assembly. The method returns an instance of the AssemblyBuilder class and receives the AssemblyName instance and a value from the AssemblyBuilderAccess enumeration that establishes the access level for Reflection. RunAndSave enables executing and saving the assembly, but you can also limit Reflection with the ReflectionOnly value. The next step is creating an instance of the ModuleBuilder class that can act as a container of types. This is accomplished by invoking the AssemblyBuilder.DefineDynamicModule method that requires you to specify the module name and the filename. (This one should be the same as for AssemblyName if you want metadata to be merged into a single assembly.) When you have a module, you can put your types into it. For each type you need to create an instance of the TypeBuilder class, which you accomplish by invoking the ModuleBuilder.DefineType method that receives the type name and qualifiers as arguments. Qualifiers are one or more values from the TypeAttributes enumeration; in the current example, Public and Class values are assigned to the new type to create a new class with public visibility. The TypeBuilder class provides lots of methods for adding members, such as constructors, field, properties, and methods. For constructors, the code demonstrates how to add a public, empty, and default constructor invoking the TypeBuilder.DefineDefaultConstructor, but you can supply constructor overloads via the DefineConstructor method. To implement properties, you first need to supply fields. These are implemented via the TypeBuilder.DefineField method that requires three arguments: the field name, the type (retrieved via GetType), and qualifiers, determined with values from the FieldAttributes enumeration. Similarly you implement properties invoking the TypeBuilder.DefineProperty method, but this is not enough because you also need to explicitly generate the getter and setter methods for each property. These are special methods that require providing some properties defined within the propMethodAttributes variable that takes values from the MethodAttributes enumeration. When you establish method attributes, you create two MethodBuilder instances. Such a class generates each kind of method, including special ones. You just supply the method name, attributes, the return type, and an array of type parameters. The actual problem is how you implement method bodies. As a general rule, methods implemented via Reflection cannot have an empty method body, so you must provide some Intermediate Language code to populate the method body. This is accomplished by invoking methods from the ILGenerator class that enable injecting IL code to the method. Consider the following snippet, excerpted from Listing 47.4:

image

The MethodBuilder.GetILGenerator method returns an instance of the ILGenerator class. Then you invoke the Emit method to execute IL code. In the preceding snippet, the IL code simply returns the value of the fldBuilder variable and pushes the value onto the stack and then returns. Actions to execute via the IL are taken via shared fields from the OpCodes class, each related to an IL instruction.

Note on Opcodes

Reflection is powerful, but because you need to know the MS Intermediate Language in detail before implementing dynamic code, and because this would be beyond of scope in this book, you should look at the appropriate MSDN documentation at http://msdn.microsoft.com/en-us/library/8ffc3x75(VS.100).aspx.

When you provide the method body for getters and setters, you add them to the related properties via the PropertyBuilder.SetGetMethod and PropertyBuilder.SetSetMethod methods. Similarly you implement any other method, and the sample code demonstrates this by providing a simple method body that invokes EmitWriteLine, a method that sends to the assembly the appropriate IL code for writing a message to the Console window. Finally you simply invoke AssemblyBuilder.Save to save the assembly to disk. More than running the code, you can ensure if everything works by inspecting the assembly with a Reflection tool such as Microsoft IL Disassembler. Figure 47.2 shows how the assembly looks if opened with ILDasm, demonstrating the correct result of our work.

Figure 47.2 The assembly created at runtime opened in IL Disassembler.

image

Typically you will prefer code generators instead of Reflection to generate code on-the-fly because in that case you do not need to know about Intermediate Language. After you define your types on-the-fly, you can then consume them using techniques described in the “Invoking Code Dynamically” section.

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

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