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.
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:
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.
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.
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.
3.133.13.76