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.
Listing 1: A simple class generator
Imports System.Reflection
Imports System.Reflection.Emit
Imports System.Threading
Module MathClassBuilder
Dim ab As AssemblyBuilder
Public Sub Main()
Dim numbers As String
Dim values() As String
Dim MathOpsClass As Type
Dim MathOpsInst As Object
Dim obj As Object
Try
Console.WriteLine("Enter values:")
numbers = Console.ReadLine()
values = Split(numbers, ",")
'create our assembly, module, etc.
' 'and return the new type
MathOpsClass = _
CreateType("OurAssembly", _
"OurModule", "MathOps", "ReturnSum", _
values)
'create an actual instance of the
'MathOps class.
MathOpsInst = _
Activator.CreateInstance(MathOpsClass, New Object() {})
'call the ReturnSum method
obj = _
MathOpsClass.InvokeMember("ReturnSum", _
BindingFlags.InvokeMethod, Nothing, _
MathOpsInst, Nothing)
Console.WriteLine("Sum: {0}", _
obj.ToString())
Catch e As Exception
Console.WriteLine(e.Message)
End Try
End Sub
Public Function CreateType(ByVal assemblyName As String, _
ByVal moduleName As String, _
ByVal className As String, _
ByVal methodName As String, _
ByVal values() As String) As Type
Dim name As New AssemblyName()
Dim ad As AppDomain
Dim mb As ModuleBuilder
Dim method As MethodBuilder
Dim theClass As TypeBuilder
Dim retType As Type
Dim generator As ILGenerator
Dim i As Int32
Try
'create a name for the assembly
name.Name = assemblyName
'get the current app domain
ad = Thread.GetDomain()
'create an assemblybuilder from the
'app domain
ab = ad.DefineDynamicAssembly(name, _
AssemblyBuilderAccess.Run)
'create a module builder instance
mb = ab.DefineDynamicModule(moduleName)
'create our class type
theClass = mb.DefineType(className, _
TypeAttributes.Public)
'define the return type
retType = GetType(System.Int32)
'create the method
method = _
theClass.DefineMethod(methodName, _
MethodAttributes.Public, retType, _
Nothing)
generator = method.GetILGenerator()
'this pushes an initial value (0) onto
'the stack
generator.Emit(OpCodes.Ldc_I4, 0)
For i = 0 To values.GetUpperBound(0)
'for each value in the array,
'push its value onto the stack
generator.Emit(OpCodes.Ldc_I4, _
CType(values(i), Int32))
'this adds the values currently
'on the stack and pushes
'the results back onto the stack.
generator.Emit(OpCodes.Add)
Next
'now, we need to return the value on
'the stack
generator.Emit(OpCodes.Ret)
'class type is finished; return the
'type
Return theClass.CreateType()
Catch e As Exception
WriteLine("An error occured: {0}", _
e.Message)
End Try
End Function
End Module
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.
Table 1: Reflection emit-related class reference.
Namespace.Class | System.Reflection.Emit.AssemblyBuilder |
---|---|
Primary Use | Defines a dynamic .NET assembly: A self-describing .NET building block. A dynamic assembly is one that was specifically generated through reflection emit. This class inherits from System.Reflection.Assembly. |
Example Call | Dim ab As AssemblyBuilderDim ad As AppDomainad `=` Thread.GetDomain()ab = ad.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run) |
Namespace.Class | System.Reflection.Emit.ConstructorBuilder |
Primary Use | Used to create and represent the constructor of a dynamic class. It encapsulates all information related to the constructor including its `name,` function signature, and code body. This is only needed if you are creating a parameter-driven constructor or you're overriding the default behavior of the parent class's constructor. |
Example Call | Dim ourClass As `TypeBuilder` `=` `[module].DefineType("ourClass",` _TypeAttributes.Public)Dim ctorArgs As `Type()` = {GetType(String)}Dim ctor As `ConstructorBuilder` `=` `_ourClass.DefineConstructor(MethodAttributes.Public,` _CallingConventions.Standard, constructorArgs) |
Namespace.Class | System.Reflection.Emit.CustomAttributeBuilder |
Primary Use | Used to create custom attributes for dynamic classes. |
Namespace.Class | System.Reflection.Emit.EnumBuilder |
Primary Use | Defines and represents an enumeration. |
Namespace.Class | System.Reflection.Emit.EventBuilder |
Primary Use | EventBuilder creates events for a dynamic class. |
Namespace.Class | System.Reflection.Emit.FieldBuilder |
Primary Use | Use FieldBuilder to define fields on a class. |
Namespace.Class | System.Reflection.Emit.ILGenerator |
Primary Use | Used to generate MSIL code (Microsoft Intermediate Language). |
Example Call | Dim gen As ILGenerator = someMethod.GetILGenerator()gen.Emit(OpCodes.Ldarg_0)gen.Emit(OpCodes.Ret) |
Namespace.Class | System.Reflection.Emit.LocalBuilder |
Primary Use | Creates variables that are local to a given method or constructor. |
Namespace.Class | System.Reflection.Emit.MethodBuilder |
Primary Use | Used to create and represent methods inside of a class. |
Example Call | Dim t as Type `=` myModule.GetType("MyClassName") |
Namespace.Class | System.Reflection.Emit.MethodRental |
Primary Use | A utility class used to swap a method into a dynamically created class from some other class template. This is useful when you need to quickly re-create functionality via a method that already exists elsewhere. |
Namespace.Class | System.Reflection.Emit.ParameterBuilder |
Primary Use | Creates parameters for use in a method signature. |
Namespace.Class | System.Reflection.Emit.PropertyBuilder |
Primary Use | Defines properties for a type. |