In our previous article, Reflection Part 1: Discovery and Execution, we introduced the System.Reflection namespace and its classes which allow developers to view assembly metadata,
query for and discover types, and invoke code - all at run-time. In this article we will examine reflection emit - the ability to dynamically generate code at run-time.
Reflection: a Review
First, a quick review of exactly what reflection is and what it is used for. From Part 1, you know that "Reflection is a means of discovering information about objects at runtime and executing against those objects." This functionality is provided in .NET by the System.Reflection namespace. Classes in this namespace, including Assembly, Module, ConstructorInfo, MethodInfo, and others, are used for type discovery and dynamic invocation. Put simply, they allow you to both explore the classes, methods, properties, and fields (types) exposed by an assembly as well as create an instance of a type and execute one of its methods (invoke members). While these features are great for run-time object discovery, reflection in .NET does not stop there. Reflection also allows you to build assemblies and create entirely new types at run-time. This is known as reflection emit.
What Is Reflection Emit?
Nested under System.Reflection is the System.Reflection.Emit namespace, which is home for all of the Framework classes that allow you to dynamically build assemblies and types from scratch. Although the very act of generating code on demand is likely a feature that few developers will ever need, it is a credit to the .NET Framework that the tools are there, waiting for the right business problem to solve.
Note that reflection emit classes do not generate source code. In other words, your efforts here won't create Visual Basic .NET or C# .NET code. Instead, your reflection emit classes will emit MSIL op codes.
As an example, using reflection emit it would be possible to:
- Create a new assembly. (Assemblies can be "dynamic" in that they exist only in memory, or can be persisted to disk.)
- Within the assembly, create a module.
- Within the module, create a type.
- Add properties and methods to that type.
- Generate the code inside of the method or property.
As it turns out this is actually the exact process you will follow when you use the Reflection.Emit classes to generate code.
Code Generation Process
Following the steps listed above, let's examine the exact operations that are necessary to build out an assembly. For the purposes of this very basic example, let's assume that you want to build a class called MathOps that has one public method (a function). This function will accept two input parameters as integers and return the sum of those two integers.
#1: Step 1: Building the Assembly
To expand slightly on the steps listed above, the actual process for step 1 looks like this:
- Create an AssemblyName. (This is used to uniquely identify and name the assembly.)
- Grab a reference to the current application domain. (The app domain will provide the actual method that returns an AssemblyBuilder object.)
- Create an AssemblyBuilder instance by calling AppDomain.DefineDynamicAssembly.
To start the assembly generation process you first need to create an AssemblyName instance that you'll use to identify your assembly.
' Create a name for the assembly.
Dim name As New AssemblyName()
name.Name = "MyAssembly"
Next, you need an instance of a System.AppDomain class. You can get this from the currently running (static) thread instance.
Dim ad As AppDomain
ad = Thread.GetDomain()
With these two elements in place you can define an AssemblyBuilder class and create an instance of it using the AppDomain and AssemblyName that you previously created. The AssemblyBuilder class is the work-horse of reflection emit. It provides the primary mechanisms you need to create new assemblies from scratch. You also need to specify an AssemblyBuilderAccess enumeration value that will indicate whether you want the assembly written to disk, saved in memory, or both. In this example, you want to save the assembly in memory.
Dim ab As AssemblyBuilder
ab = ad.DefineDynamicAssembly(name, _
AssemblyBuilderAccess.Run)
Step 2: Defining a Module
In Step 2 you'll use the ModuleBuilder class to create a dynamic module inside of the assembly that you previously created. ModuleBuilder creates modules inside of an assembly. Calling the DefineDynamicModule method of the AssemblyBuilder object will return an instance of a ModuleBuilder. As with the assembly, you must give the module a name (although here, the name is just a string).
Dim mb As ModuleBuilder
mb = ab.DefineDynamicModule("MyModule")
Step 3: Creating a Type
Now that you have an assembly and a module, you can add your class into the assembly. To build a new type (in your case you're building a MyClass type), you use the TypeBuilder class. You'll use a method from the "parent" object to actually return an instance of the builder object.
Dim theClass As TypeBuilder
theClass = mb.DefineType("MathOps", _
TypeAttributes.Public)
Note that you've specified the visibility of the type as public by using the TypeAttributes enumeration.
Step 4: Adding a Method
With the class type created, you can now add your method to the class. Call the method ReturnSum, and create it as a public function.
Use the MethodBuilder class to specify methods for a specific type. You can create a MethodBuilder instance by calling DefineMethod on your previously created type object. DefineMethod expects four parameters: the method, any attributes the method might possess (such as public, private, etc.), parameters, and a return type. Parameters and return type can be void values in the case of a subroutine. In your case you're creating a function so you need to specify both the parameters and return type values.
To specify the return type, create a type object that holds a return type value (a System.Int32 value).
Dim retType As Type
retType = GetType(System.Int32)
You'll use an array of type values to specify the parameters to the function. Both of the parameters are int32 values as well.
Dim parms As Type()
parms(0) = GetType(System.Int32)
parms(1) = GetType(System.Int32)
With these items in hand you can now call DefineMethod.
Dim mb As MethodBuilder = _
theClass.DefineMethod("ReturnSum", _
methodAttributes.Public, retType, parms)
#2: Step 5: Generating Code
Since you have, in essence, stubbed out the function in step 4, you now need to add the actual code body to the method. This is really the core of the code generation process with reflection emit.
It is important to note that reflection emit classes do not generate source code. In other words, your efforts here won't create Visual Basic .NET or C# .NET code. Instead, your reflection emit classes will emit MSIL op codes. MSIL (Microsoft Intermediate Language) is an intermediate code language that closely resembles assembler. MSIL is consumed by the .NET JIT compiler when it creates native binaries. Op codes are low-level, assembler-like operating instructions.
Consider the following implementation of ReturnSum:
Function ReturnSum(ByVal val1 As Integer, _
ByVal val2 As Integer) As Integer
Return val1 + val2
End Function
If you want to emit this code, you would first need to figure out how to write this function using only MSIL op codes. Thankfully, there is a quick and easy way to do this. You can simply compile the code and examine the resulting assembly using the .NET Framework ILDASM.exe utility. Compiling the function above yields the following MSIL version:
.method public instance int32 ReturnSum(int32 val1,
int32 val2) cil managed
{
// Code size 9 (0x9)
.maxstack 2
.locals init ([0] int32 ReturnSum)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: ldarg.2
IL_0003: add.ovf
IL_0004: stloc.0
IL_0005: br.s IL_0007
IL_0007: ldloc.0
IL_0008: ret
} // end of method MathOps::ReturnSum
To emit this code you will use the ILGenerator class. You can retrieve an instance of the ILGenerator class specific to your method by calling the MethodBuilder.GetILGenerator() method. In other words, the following code will obtain an ILGenerator instance for your ReturnSum method.
Dim gen As ILGenerator
gen = mb.GetILGenerator()
Using the generator object you can inject op code instructions to the method.
gen.Emit(OpCodes.Ldarg_1)
gen.Emit(OpCodes.Ldarg_2)
gen.Emit(OpCodes.Add_Ovf)
gen.Emit(OpCodes.Stloc_0)
gen.Emit(OpCodes.Br_S)
gen.Emit(OpCodes.Ldloc_0)
gen.Emit(OpCodes.Ret)
At this point you've essentially finished creating the function, class, module, and assembly. To get a reference to this class you can make one call to CreateType like this:
theClass.CreateType()
#3: Namespaces and Classes
As you've already seen, the Reflection.Emit namespace contains a core set of "builder" classes that you use to create types and the various attributes, methods, fields, properties, and so on that are associated with the new type. Table 1 describes the primary classes you'll use when emitting code with reflection.
Coupling Emit and Dynamic Invocation
Now that you know how to emit a dynamic assembly using the Reflection classes, let's integrate the concept of dynamic invocation (covered in Part 1 of this article).
One example of when to use Reflection and Reflection.Emit is with code or script evaluation at run-time. It would be possible, for instance, to display a Windows form with a text box, ask the user to type in a formula, and then evaluate that formula at run-time through compiled code.
Another time to use Reflection.Emit classes is to optimize performance. Code solutions, by design, tend to be generalized solutions to a problem. This is often a good thing from a design perspective because it makes your systems flexible. For instance, if you want to compute the sum of numbers and you don't necessarily know at design time how many values you need to sum, then you may need to call a loop. If you rewrite the ReturnSum function to accept an array of integers, you could loop through the members of that array, add each to a counter value, and then return the sum of all of the values. This is a nice generic solution since it doesn't care about the total number of values contained in the array.
Public Function ReturnSum(ByVal values() _
As Integer) As Int32
Dim i As Int32
Dim retValue As Int32
For i = 0 To values.GetUpperBound(0) - 1
retValue = retValue + values(i)
Next
Return retValue
End Function
On the other hand, if you hard-code the array limit you can return the sum in a more optimized fashion by writing one long math operation statement. For a few values or even hundreds of values the difference would be negligible. But, if you're dealing with thousands or millions of values, the hard-coded method will be much, much faster. In fact, you can make this solution even faster by pulling out the array values and summing them with straight number addition while at the same time eliminating any 'zero' values that won't affect the result anyway:
Public Function ReturnSum() As Int32
Return 9 + 32 + 8 + 1 + 2 + 2 + 90 '...
End Function
The problem here, of course, is that you've written code that is not general and is not flexible.
So how do you get the best of both worlds? Answer: Reflection.Emit.
By combining Reflection.Emit functionality (such as taking in an array ceiling and array values, and then compiling the code), and Reflection functionality (such as locating, loading, and running the assembly that was emitted), you can craft some pretty ingenious performance solutions while avoiding brittle code. In this simple case you could write a loop that generates the MSIL op codes you need.
Consider the following console application that consumes an array and creates a new assembly, module, class, and ReturnSum method that directly sums the array values without resorting to a loop. The code in Listing 1 showcases many of the same concepts you've already seen in terms of using the emit "builder" objects. Please note some new code that is introduced with the application: Activator.CreateInstance is used to create an instance of the newly created object type, and the InvokeMember method is used to call the ReturnSum method on the type.
Summary
Reflection and Reflection.Emit namespaces allow programmers to dynamically generate and execute managed code at run-time. They provide a highly specialized set of classes for dealing with a uniquely specialized set of business problems.
You can download the code for this article at www.brilliantstorm.com/resources.