When you start learning any language you start having to understand the underlying type system that the language employs. C# is no different and stored variables contain meta data within the CLR to help guide the runtime. This is ever generated at compile time if the variable is strongly typed; or implied via a dynamic type allocation, or during runtime.
This information and much more is available through System.Reflection, a much understated tool which has enormous functionality. Using it you can rip apart Assembies, AppDomains and methods to see how they work, dynamically call and allocate variables and jump between Managed and the much more painful unmanaged code.
What I would like to talk about is how we can use reflection to dynamically generated Microsoft Intermediate Language (MSIL) code and affectively build applications at runtime.
To put it simply ‘Gentlemen we have the technology, we can rebuild you’
To start off with we need to import references to Reflection and Reflection.Emit which are used to generate MSIL code and access Reflection functionality.
using System.Reflection; using System.Reflection.Emit;
Now the fun begins. The code we write is wrapped into a dynamic Assembly, this assembly then is required to be housed into an AppDomain, for convience I will just attach it to the main AppDomain of the running application (AppDomain.CurrentDomain) however it can be advised for security reasons (if you are not sure the context of the code in which you generate) to generate and call across to a different AppDomain.
AssemblyName name = new AssemblyName("ExampleAssemblyName"); // If required you can set attributes to 'name' to define the assembly name.Version = new Version(0, 1); AssemblyBuilder DynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
AssemblyBuilder is used to allow to us to dynamically import content into the assembly. AssemblyBuilderAccess flags instruct the CLR on how to generate the MSIL code and metadata as not all of it may be required in differing situations.
Just like AssemblyBuilder, you will notice any of the method, type or class builders end in ‘Builder’, this is to let the programmer understand this doesn’t contain assembly, class etc. information but methods to build them.
Now we have a dynamica assembly, lets fill it with content, for this example lets dynamically build hello world…
To get to the point where we can write the output of ‘Hello World’ to the console, we need to create a module, then class, and then a method contained within. Quite a few layers of hierarchy.
ModuleBuilder mb = DynamicAssembly.DefineDynamicModule("dynMod"); TypeBuilder tb = mb.DefineType("dynType", TypeAttributes.Class); ConstructorBuilder cb = tb.DefineDefaultConstructor(MethodAttributes.Public); MethodBuilder method = tb.DefineMethod("Greet", MethodAttributes.Public | MethodAttributes.Static);
We are calling our Public class ‘dynType’ and inserting a Public Static Method called Greet. As you can see by the ordering:
- Module contains Class
- Class contains Method
- Method contains Code
Now we have got to the method called, we finally can call upon the IL generator to fill it with content.
ILGenerator dynCode = method.GetILGenerator();
The ILGenerator contains all the functionality to write any code at a lower level that you can normally in C#, this is because C# compiles down to IL. The only issue is understanding how to achieve the same functionality, a great way to find out more info is to either mess around or use the IL dissembler.
The functions within the ILGenerator object which you will mostly use are .Emit and .EmitWriteLine. Below is the code to write out ‘Hello World’ to the console.
dynCode.EmitWriteLine("Hello world!"); dynCode.Emit(OpCodes.Ret);
Although not required as a method automatically returns, I thought I would show how to emit a function method, in this case a return call. If you look through the OpCodes enumeration you will see all the different OpCodes that are available to you to generate your code.
Now that we have all the code necessary to generate a dynamic assembly and code within, we need to call it.
Again all this can be done dynamically from code. This like everything else I mention is done via reflection. Below is the code to generate an instance of the class (although you could to it at the Assembly or Method level) and then finds the correct method and calls it.
Type myDynType = tb.CreateType(); myDynType.GetMethod("Greet").Invoke(null, null);
If you put all this code together you should see ‘Hello World’ written out to the console.