Category Archives: IL

Generating IL using C#

Note: This is an old post I had sitting around for a couple of years, I’m not sure how complete or useful it is, but better being published than hidden away and it might be of use at some point.

There are different ways to dynamically generate code for .NET, using tools such as T4, custom code generators run via target builds etc. Then there’s creating your assembly, modules, types etc. via IL. I don’t mean literally write IL files but instead generating your IL via C# using the ILGenerator class and Emit methods.

I wanted to write a factory class that worked a little like Refit in that you define the interface and Refit “magically” creates an implementation to the interface and calls boilerplate code to inject and/or do the work required to make the code work.

Refit actually uses build targets and code generation via GenerateStubsTask and InterfaceStubGenerator not IL generation.

IL is not really a simple way to achieve these things (source generators, currently in previous, would be far preferable) but maybe in some situations IL generation suits your requirements and I thought it’d be an interesting thing to try anyway.

Use case

What I want to do is allow the developer to create an interface which contains methods (we’re only going to support “standard” methods at this point). The methods may take multiple arguments/parameters and must return Task (for void) or Task of T (for return values). Just like Refit, the idea would be that the developer marks methods in the interface with attributes which then tell the factory class what code to generate for the implementation.

All very much along the lines of Refit.

Starting things off by creating our Assembly

We’re going to need to create an Assembly, at runtime, to host our new types, so the first thing we do is, using the domain of the current thread we’ll use the method DefineDynamicAssembly, pass in both an AssemblyName and AssemblyBuilderAccess parameter which creates an AssemblyBuilder. This becomes the starting point for the rest of our builders and eventually our IL code.

Note: If you want to save the assembly to disk, which is very useful for debugging by inspecting the generated code using ILSpy or the likes, then you should set the AssemblyBuilderAccess to AssemblyBuilderAccess.RunAndSave and supply the file path (not the filename) as the fourth argument to DefineDynamicAssembly.

Before we get into this code further, let’s look at a simple interface which will be our starting point.

public interface IService
{
   Task<string> GetName();
}

Whilst the aim, eventually, is to include attributes on our interface and return different generic types, for this post we’ll not get into this, but instead simply generate an implementation which ignores the arguments passed and expects either a return of Task or Task<string>.

Let’s create our assembly – here’s the code for the TypeGenerator class.

public class TypeGenerator
{
   private AssemblyBuilder _assemblyBuilder;
   private bool _save;
   private string _assemblyName;

   public TypeGenerator WithAssembly(string assemblyName, string filePath = null)
   {
      var currentDomain = Thread.GetDomain();
      _assemblyName = assemblyName;
      _save = !String.IsNullOrEmpty(filePath);

      if (_save)
      {
         _assemblyBuilder = currentDomain.DefineDynamicAssembly(
            new AssemblyName(_assemblyName),
               AssemblyBuilderAccess.RunAndSave,
                  filePath);
      }
      else
      {
         _assemblyBuilder = currentDomain.DefineDynamicAssembly(
            new AssemblyName(_assemblyName),
               AssemblyBuilderAccess.Run);
      }
      return this;
   }

   public static TypeGenerator Create()
   {
      return new TypeGenerator();
   }
}

The code above will not actually save the assembly but is part of the process we need to go through to actually save it. Let’s add a save method which will actually save the assembly to disk.

public TypeGenerator Save()
{
   if (!String.IsNullOrEmpty(_assemblyName))
   {
      _assemblyBuilder.Save(_assemblyName);
   }
   return this;
}

Note: we’ll also need to assign the assembly name to the Module which we’re about to create.

Now we need a Module

Creating the module is simply a case of calling DefineDynamicModule on the AssemblyBuilder that we created, this will give us a ModuleBuilder which is where we’ll start generating our type code.

As noted, if we are saving the module then we also need to assign it the assembly name, so here’s the code for creating the ModuleBuilder

public TypeGenerator WithModule(string moduleName)
{
   if (_save)
   {
      _moduleBuilder = _assemblyBuilder.DefineDynamicModule(
         moduleName, _assemblyName);
   }
   else
   {
      _moduleBuilder = _assemblyBuilder.DefineDynamicModule(
         moduleName);
   }
   return this;
}

Creating our types

Considering this post is about IL code generation, it’s taken a while to get to it, but we’re finally here. We’ve created the assembly and within that a module. Our current implementation for generating a type will take the interface as a generic parameter (only interfaces will be handled), here’s the method

public TypeGenerator WithType<T>()
{
   var type = typeof(T);

   if (type.IsInterface)
   {
      EmitTypeFromInterface(type);
   }

   return this;
}

The EmitTypeFromInterface will start by defining a new type using the ModuleBuilder. We’ll create a name based upon the interface type’s name. Obviously the name needs to be unique. To make things simple we’ll just prefix the text “AutoGenerated”, hence type IService will become implementation AutoGeneratedIService. We’ll also need to set up the TypeAttributes to define our new type as a public class and in our case ensure the new type extends the interface. Here’s the code to generate a TypeBuilder (and also create the constructor for the class)

private void EmitTypeFromInterface(Type type)
{
   _typeBuilder = _moduleBuilder.DefineType($"AutoGenerated{type.Name}",
      TypeAttributes.Public |
      TypeAttributes.Class |
      TypeAttributes.AutoClass |
      TypeAttributes.AnsiClass |
      TypeAttributes.BeforeFieldInit |
      TypeAttributes.AutoLayout,
      null, new[] { type });


   var constructorBuilder =
      _typeBuilder.DefineDefaultConstructor(
         MethodAttributes.Public |
         MethodAttributes.SpecialName |
         MethodAttributes.RTSpecialName);

   // insert the following code snippets here
}

Implementing our methods

Obviously an interface requires implementations of it’s methods – yes you can actually save the assembly without supplying the methods and will get a TypeLoadException stating that the new type does not have an implementation for the method.

In the code below we’ll look through the methods on the interface type and using the TypeBuilder we’ll create a MethodBuilder per method which will have the same name, return type and parameters and will be marked as public and virtual, from this we’ll finally get to emit some IL using the ILGenerator. Here’s the code

foreach (var method in type.GetMethods())
{
   var methodBuilder = _typeBuilder.DefineMethod(
      method.Name,
      MethodAttributes.Public |
      MethodAttributes.Virtual,
      method.ReturnType,
      method.GetParameters().Select(p => p.ParameterType).ToArray());

   var ilGenerator = methodBuilder.GetILGenerator();

   // IL Emit code goes here
}

A very basic overview or writing IL code

We can generate IL code using an ILGenerator and Emit methods from a C# application (for example). We can also write IL directly as source code files. For example, create a file test.il

Now add the following code

.assembly MyAssembly
{
}

.method void Test()
{
.entrypoint
ret
}

The text preceded by the . are directives for the IL compiler (ILASM which comes with Visual Studio). Within the file we’ve firstly declared an assembly named MyAssembly. Whilst this file would compile without the .assembly, it will not run and will fail with a BadImageFormatException.

Next we define a method (using the .method directive) named Test. The .entrypoint declares this is the entry point to our application (as this will compile to an EXE). Hence unlike C# where we use Main as the entry point, any method may be the entry point but only one method may be marked as the entry point.

To create a correctly formed method we also need the last line code to be a ret.

If you now compile this file using

ilasm test.il

You might notice that ilasm outputs the warning Non-static global method ‘Test’, made static. Obviously in C# our entry method would normally be a static method. Simply add the keyword static as below

.method static void Test()
{
.entrypoint
ret
}

Let’s now turn this little IL application into the classic Hello World by calling the Console.WriteLine method.

If you’ve ever written any assembly code you’ll know we pass arguments to subroutines by placing the arguments on the stack and then the callee will pop the expected number of arguments. So to output a string, we’ll need to push it onto the stack – in this case we use ldstr which specifically handles strings.

Console.WriteLine is available in the System namespace within mscorlib, and to invoke a method we’ll need to call it specifying the overload (if any) to use along with a fully qualified name, hence our Test method becomes

.method static void Test() 
{
.entrypoint

ldstr "Hello World"
call void [mscorlib]System.Console::WriteLine(class System.String)
ret
}

The easiest way to learn IL is to look at decompilation from tools such as ildasm, ILSpy, Reflector or dotPeek, write code you wish to generate IL for, compile then decompile with one of these tools to see what’s going on.