Reflection.Emit Hello World for Beginners

Lets start by saying the Reflection.Emit is inherently one of the most complex portions of the C# language. You’ll be able to undermine everything that the .Net designers intended and create complex difficult to debug code which your coworkers will hate. This article demonstrates a simple “Hello World” program.

Here is a little background before getting started.

  • Intermediate Language: In the java world, this is called “byte code”. It is a high level assembly language, which .Net code is translated into, before being compiled into machine code.
  • Execution Stack: This is where .Net does it’s work. Variables and references are pushed onto and popped off of the execution stack during execution. This is how arguments are passed to method calls. The method arguments are pushed onto the execution stack, and popped off by the method call.

Lets get started with a simple demonstration of a new Assembly, new Module, and a simple global method which prints out “Hello World Global Method!”

// Define a new assembly, named "TestAssembly".
var assemblyName = new AssemblyName("TestAssembly");
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect);
var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);

// Define a new global method named "HelloWorld".
var globalMethod = moduleBuilder.DefineGlobalMethod(
    name: "HelloWorld",
    attributes: MethodAttributes.Public | MethodAttributes.Static,
    returnType: typeof(void),
    parameterTypes: Type.EmptyTypes);

// Get the IL (intermediate language) generator for the method, so we can start working with intermediate language.
var ilGenerator = globalMethod.GetILGenerator();

// Get the method info for "Console.WriteLine(string)";
var writeLine = typeof(Console).GetMethod(
    name: nameof(Console.WriteLine),
    bindingAttr: BindingFlags.Static | BindingFlags.Public,
    binder: Type.DefaultBinder,
    types: new[] { typeof(string) },
    modifiers: null);

// Load the string "Hello World Global Method!" onto the execution stack.
ilGenerator.Emit(OpCodes.Ldstr, "Hello World Global Method!");

// Call Console.Writeline(string). This will pop off the string we just loaded onto the execution stack.
ilGenerator.Emit(OpCodes.Call, writeLine);

// Return.
ilGenerator.Emit(OpCodes.Ret);

// Global functions must be "done" before the module can be officially created.
// This call will create/compile them all.
moduleBuilder.CreateGlobalFunctions();

// Get the method we just created
var resultingMethod = moduleBuilder.GetMethod("HelloWorld");

// Create a delegate. This method takes no arguments and doesn't return a value, so its the same as an "Action".
var resultingDelegate = (Action)resultingMethod.CreateDelegate(typeof(Action));

// Call it.
resultingDelegate();

This was a very simple method and creating a dynamic assembly was too much work. So here is an example of the same thing, but using the “DynamicMethod” class. “DynamicMethod” is powerful in those sort of scenario, when creating simple static methods.

// Define a new assembly, named "TestAssembly".
var assemblyName = new AssemblyName("TestAssembly");
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect);
var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);

// Define a new global method named "HelloWorld".
var globalMethod = moduleBuilder.DefineGlobalMethod(
    name: "HelloWorld",
    attributes: MethodAttributes.Public | MethodAttributes.Static,
    returnType: typeof(void),
    parameterTypes: Type.EmptyTypes);

// Get the IL (intermediate language) generator for the method, so we can start working with intermediate language.
var ilGenerator = globalMethod.GetILGenerator();

// Get the method info for "Console.WriteLine(string)";
var writeLine = typeof(Console).GetMethod(
    name: nameof(Console.WriteLine),
    bindingAttr: BindingFlags.Static | BindingFlags.Public,
    binder: Type.DefaultBinder,
    types: new[] { typeof(string) },
    modifiers: null);

// Load the string "Hello World Global Method!" onto the execution stack.
ilGenerator.Emit(OpCodes.Ldstr, "Hello World Global Method!");

// Call Console.Writeline(string). This will pop off the string we just loaded onto the execution stack.
ilGenerator.Emit(OpCodes.Call, writeLine);

// Return.
ilGenerator.Emit(OpCodes.Ret);

// Global functions must be "done" before the module can be officially created.
// This call will create/compile them all.
moduleBuilder.CreateGlobalFunctions();

// Get the method we just created
var resultingMethod = moduleBuilder.GetMethod("HelloWorld");

// Create a delegate. This method takes no arguments and doesn't return a value, so its the same as an "Action".
var resultingDelegate = (Action)resultingMethod.CreateDelegate(typeof(Action));

// Call it.
resultingDelegate();

Another option to consider, if your use case is just simple static methods, is to use the System.Linq.Expressions namespace. This will create much more readable code, with much better supported debugging ability. An Expression tree will also show you the C# code which is generated behind the scenes, so you don’t need to work in IL.

// Get the method info for "Console.WriteLine(string)";
var writeLine = typeof(Console).GetMethod(
    name: nameof(Console.WriteLine),
    bindingAttr: BindingFlags.Static | BindingFlags.Public,
    binder: Type.DefaultBinder,
    types: new[] { typeof(string) },
    modifiers: null);

// Generate a lamba expression, which calls writeline, passing a single constant argument.
var lambdaExpression = Expression.Lambda(
    body: Expression.Call(
        method: writeLine, 
        arg0: Expression.Constant("Hello World Expression!")
    ));

var resultingMethod = (Action)lambdaExpression.Compile();
resultingMethod();

Take a look at “lambdaExpression.ToString()”. In the debugger, you will see that the underlying code generated is: “{() => WriteLine(“Hello World Expression!”)}”

Theres a quick tutorial on Reflection.Emit! In most code, using this functionality of .Net is overkill, and will cause maintainability nightmares. The best way to learn this stuff is to write some C# code, which looks like the code you’re trying to generate, and use a decompiler to look at the resulting IL.

Leave a Reply

Your email address will not be published. Required fields are marked *