Read part I here.
Introduction
MSDN says:
The System.Reflection.Emit
namespace contains classes that allow a compiler or tool to emit metadata and Microsoft intermediate language (MSIL) and optionally generate a PE file on disk. The primary clients of these classes are script engines and compilers.
Borrowing again from MSDN, Reflection emit provides the capabilities to:
- Define lightweight global methods at run time, using the DynamicMethod class, and execute them using delegates.
- Define assemblies at run time and then run them and/or save them to disk.
- Define modules in new assemblies at run time and then run and/or save them to disk.
- Define types in modules at run time, create instances of these types, and invoke their methods.
- Define symbolic information for defined modules that can be used by tools such as debuggers and code profilers.
Well all of this sounds cool and way geeky, isn't it? I would probably write about all these capabilities some day, but right now my objective is to use assembly generation capabilities of Reflection.Emit
to generate an assembly which is similar to one we produced using CodeDOM and dynamic code compilation in part I of this article. If you have not already read it, you can find it here.
Our generated assembly would be the same as we generated in Part I. But how do we do that? "Emitting" an assembly using Reflection.Emit
classes is also a multistep process like CodeDOM. In this part also, we would directly jump to implementing the GenerateAssembly
method using Reflection.Emit
in a class called MSILGenerator
.
Anatomy of Reflection.Emit
Reflection.Emit
namespace contains several classes called as "builders" to generate various constructs like methods, types (or classes), constructors, assembly, etc. These builders provide an ILGenerator
which can be used to emit required opcodes. How in the world would we know which opcodes to generate. And how do we ensure that they are correct?
As recommended by several others, I also feel the quickest way to ride this learning curve is to write simple code (an assembly with a blank class for example), build it and inspect it using dis-assembler - ILDASM. Then we can attempt to generate a similar assembly using Reflection.Emit
.
Since our aim in this article is to generate a similar assembly as would be generated using CodeDOM in part I, let us have a ILDASM peek at one of the generated assemblies. It looks something like this:
Our approach is to use Reflection.Emit
to generate the same code as shown here in the same order. Section A, B & C attempt precisely the same. There is direct correlation in what is shown in the above image and the code I have written in these sections to generate the same.
One more important thing to note is that we do not need to generate each and every line of MSIL. Reflection.Emit
hides several complexities from us. More on this when we generate some code below.
Any executable .NET code requires proper context of app domain, assembly, module, type and a method. The actual code which is written within the boundaries of method is the last step we would perform. The following image borrowed from Dr. Dobb's article on Generating Code at Run Time With Reflection.Emit shows the set of builder classes provided by Reflection.Emit
to facilitate this process of code generation.
A. Define Assembly
We can define assembly in the current app domain OR a new app domain. New app domain would give us control over loading and "unloading" of dynamic assemblies which we may want to do in real applications. For our example, I am using the current app domain.
#region Define Assembly
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "Derived";
AssemblyBuilder assemblyBuilder = currentDomain.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.Save);
Type[] constructorParameters = new Type[] { typeof(string), typeof(GenerationMode) };
ConstructorInfo constructorInfo =
typeof(AssemblyLevelAttribute).GetConstructor(constructorParameters);
CustomAttributeBuilder assemblyLevelAttribute = new CustomAttributeBuilder(
constructorInfo,
new object[] { assemblyAuthorName, GenerationMode.IL });
assemblyBuilder.SetCustomAttribute(assemblyLevelAttribute);
#endregion
Reflection.Emit
provides several classes called as "builders" to create different programming constructs like class, methods, constructors, properties, etc.
You might have guessed that the first thing we would need is the AssemblyBuilder
class. AssemblyBuilder
lets you define the target assembly. It needs an AssemblyName
(this is not a simple string
but an object of type AssemblyName
). AssemblyBuilder
constructor also asks us to specify the AccessLevel
for the generated assembly (whether we want to run it/save it/both/reflection only). Since we are just interested in generating the assembly and not running it, I have chosen Save
as the assembly access level.
We now need to apply the assembly level attribute to our defined assembly. We have a class called CustomAttributeBuilder
exactly for this purpose. For creating an object of CustomAttributeBuilder
, we need ConstructorInfo
for the type defining the custom attribute. We do this here by calling the GetConstructor
method of the type. This constructor info along with the object array of parameter values for the type constructor are then fed to custom attribute builder. Finally we set this assembly level custom attribute to our assembly using the SetCustomAttribute
method of assembly builder.
B. Define Module
#region Define module (code container)
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule
( "DerivedAssembly", "DerivedAssembly.dll");
#endregion
The next step is to create a module which acts as a container of our code. This is done using ModuleBuilder
.
C. Define Type (Class)
Steps:
- Use
TypeBuilder
to define the type. It is an overloaded method (6 overloads), we are using the one which takes the name of the type (Class name), attributes and the base type.
- Define the class level attribute using
CustomAttributeBuilder
like in section #A. Apply the custom attribute to type builder.
#region Create the derived class with the entity subscription attribute
TypeBuilder typeBuilder = moduleBuilder.DefineType("Derived",
TypeAttributes.Public | TypeAttributes.BeforeFieldInit,
typeof(Base.Base));
constructorParameters = new Type[] { typeof(string) };
constructorInfo = typeof(ClassLevelAttribute).GetConstructor(constructorParameters);
CustomAttributeBuilder classLevelAttributeBuilder =
new CustomAttributeBuilder( constructorInfo,
new object[] { classAuthorName });
typeBuilder.SetCustomAttribute(classLevelAttributeBuilder);
#endregion
D. Class Constructor
The next step is to define the class constructor. A default constructor is automatically supplied, however it is important to learn how to create a constructor if we need a parameterized one (or want to modify the behavior of the default constructor).
Our target MSIL code is shown below:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void
[DynamicCodeGeneration.Base]DynamicCodeGeneration.Base.Base::.ctor()
IL_0006: ret
}
As I mentioned previously, we are not bothered about generating each line. The non-bold lines above are taken care of by the IL generator. We just need to output the opcodes for actual operations, other plumbing tasks are handled transparently.
The steps to generate the above MSIL (part in bold is what we control/generate) are:
- Use
ConstructorBuilder
class to define the constructor in the type using the DefineConstructor
method of type builder.
- Now we are ready to start "emitting" assembly.
- Get the IL generator from constructor builder and emit the required opcodes shown above:
#region Constructor
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
System.Type.EmptyTypes);
ILGenerator generator = constructorBuilder.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Call,
typeBuilder.BaseType.GetConstructor(System.Type.EmptyTypes));
generator.Emit(OpCodes.Ret);
#endregion
E. Overriding Method
We now need to define the overriding method in the derived class. MSIL for this:
.method public hidebysig virtual instance void
Method() cil managed
{
.custom instance void [DynamicCodeGeneration.CustomAttributes]
DynamicCodeGeneration.CustomAttributes.MethodLevelAttribute::.ctor(valuetype
[DynamicCodeGeneration.CustomAttributes]
DynamicCodeGeneration.CustomAttributes.ComplexityLevel,
string) = ( 01 00 02 00 00 00 07 54 72 69 6E 69 74 79 00 00 )
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [DynamicCodeGeneration.Base]DynamicCodeGeneration.Base.Base::Method()
IL_0006: ret
}
The steps to generate the above are as follows:
- Create
MethodBuilder
object using DefineMethod
- Apply the method level attribute in a similar manner as in section #A and section #C.
- Get the
ILGenerator
for method and emit the required opcodes:
#region Method
MethodBuilder methodBuilder = typeBuilder.DefineMethod("Method",
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual,
null,
new Type[] { });
constructorParameters = new Type[] { typeof(ComplexityLevel), typeof(string) };
constructorInfo = typeof(MethodLevelAttribute).GetConstructor(constructorParameters);
CustomAttributeBuilder methodAttributeBuilder = new CustomAttributeBuilder(
constructorInfo,
new object[] { ComplexityLevel.SuperComplex, methodAuthorName });
methodBuilder.SetCustomAttribute(methodAttributeBuilder);
generator = methodBuilder.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Call, typeof(Base.Base).GetMethod("Method"));
generator.Emit(OpCodes.Ret);
#endregion
F. Wrap Up
After we have emitted all the required opcodes, we need to close the type. This operation puts a lid on the class and it is not possible to emit any more opcodes in the type. At this point, we can choose to save the assembly.
typeBuilder.CreateType();
assemblyBuilder.Save("Derived.dll");
return "Derived.dll";
Conclusion
This concludes this two-part article. We saw how we can leverage CodeDOM & Reflection.Emit
techniques to generate assemblies dynamically. We saw how to apply custom attributes at assembly, class and method. This lends us a very powerful mechanism which we can leverage in our work in so many different ways. There are so many potential uses of dynamic code generation and with .NET it is not only easy, but a lot of fun too!
Disclaimer
While writing these articles, I have freely borrowed from several resources available online. Though I have made every attempt not to leave any article in my references list, I might have inadvertently missed some citations. Please do bring to my notice such omissions and I would be glad to include the same in my article references.
References
- Generating Code at Run Time With Reflection.Emit by Chris Sells & Shawn Van Ness on Dr. Dobb's web portal
- MSDN
- Steve Eichert's views on dynamically generating assemblies
Further Reading
- CodeDOM patterns by Oman van Kloeten
- Tonnes of articles at Code Generation Network
- Introduction to Creating Dynamic Types with
Reflection.Emit
- EmitHelper
About Proteans Software Solutions
Proteans is an outsourcing company focusing on software product development and business application development on Microsoft Technology Platform. Proteans partners with Independent Software Vendors (ISVs), System Integrators and IT teams of businesses to develop software products. Our technology focus and significant experience in software product development - designing, building, and releasing world-class, robust and scalable software products help us to reduce time-to-market, cut costs, reduce business risk and improve overall business results for our customers. Proteans expertises in development using Microsoft .NET technologies.
History
- 6th May, 2007: Initial post