Defining types at runtime? why not...

atleastitry

Matt Hope

Posted on May 31, 2020

Defining types at runtime? why not...

I recently decided to undertake some interesting work around building a library .Net Core style host builder for Xamarin apps. As part of that work I encountered a strange hurdle. Essentially, I needed to define a brand new type that extended a base class and implemented an interface...all at runtime. At first, it seemed better to completely avoid this scenario; however, in the goal of complete simplicitly and readability of the end-user code needed for this library, I decided to tackle this challenge head on!

Step 1 - To actually define a type at runtime...
The first task was of course to actually build this mysterious type at runtime. Luckily System.Reflection.Emit has us covered. This assembly allows us to generate in-memory assemblies and take use of the TypeBuilder. To generate a simple type looks something like this:

public Type GenerateType() 
{
   // Build the dynamic assembly
   var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly("SuperFancyAssembly", AssemblyBuilderAccess.Run);

   // Build the dynamic type
   var typeBuilder = assemblyBuilder.DefineDynamicModule("SuperFancyModule")
        .DefineType($"SuperFancyType");

   return typeBuilder.CreateTypeInfo();
}

Enter fullscreen mode Exit fullscreen mode

Step 2 - Inherit the base class and implement the interface
Of course, generating a type is all fine and dandy but it is not quite as fun if we do not inherit a class or implement an interface. Doing that is actually much simpler than it sounds. Take a look:

public Type GenerateType() 
{
   // Build the dynamic assembly
   var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly("SuperFancyAssembly", AssemblyBuilderAccess.Run);
   // Build the dynamic type
   var typeBuilder = assemblyBuilder.DefineDynamicModule("SuperFancyModule")
        .DefineType($"SuperFancyType");

   // Add the interface implementation
   typeBuilder.AddInterfaceImplementation(typeof(ISuperFancyInterface));

   // Inherit the base class
   typeBuilder.SetParent(typeof(SuperFancyBaseClass));

   return typeBuilder.CreateTypeInfo();
}

Enter fullscreen mode Exit fullscreen mode

Step 3 - Inject IL to generate pass-through constructors (The worst bit)
This is where things get a little bit more complicated.The first thing we need to do is to copy over the constructors from our SuperFancyBaseClass:

// Get all of the constructors from the SuperFancyBaseClass type
var constructors = typeof(SuperFancyBaseClass).GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

// Loop through each constructor
foreach (var constructor in constructors)
{
   // Get all of the parameters from the constructor
   var parameters = constructor.GetParameters();

   // Get all of the types from parameters
   var parameterTypes = parameters.Select(p => p.ParameterType).ToArray();

   // Build the new constructor on the new type we are creating
   var newConstructor = typeBuilder.DefineConstructor(MethodAttributes.Public, constructor.CallingConvention, parameterTypes);

   // Loop through each parameter in the constructor
   for (var i = 0; i < parameters.Length; ++i)
   {
      var parameter = parameters[i];
      // Define the parameter
      var parameterBuilder = newConstructor.DefineParameter(i + 1, parameter.Attributes, parameter.Name);
   }
}
Enter fullscreen mode Exit fullscreen mode

Now we have defined the constructors, we need to generate the IL to call into our base constructors:

// Get the IL generator from the new constructor we defined earlier
var emitter = newConstructor.GetILGenerator();
emitter.Emit(OpCodes.Nop);

// Load `this` and call base constructor with arguments
emitter.Emit(OpCodes.Ldarg_0);
for (var i = 1; i <= parameters.Length; ++i)
{
    emitter.Emit(OpCodes.Ldarg, i);
}
emitter.Emit(OpCodes.Call, constructor);

emitter.Emit(OpCodes.Ret);
Enter fullscreen mode Exit fullscreen mode

Once we put it all together it should look something like this:

public Type GenerateType() 
{
   // Build the dynamic assembly
   var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly("SuperFancyAssembly", AssemblyBuilderAccess.Run);
   // Build the dynamic type
   var typeBuilder = assemblyBuilder.DefineDynamicModule("SuperFancyModule")
        .DefineType($"SuperFancyType");

   // Add the interface implementation
   typeBuilder.AddInterfaceImplementation(typeof(ISuperFancyInterface));

   // Inherit the base class
   typeBuilder.SetParent(typeof(SuperFancyBaseClass));

   // Get all of the constructors from the SuperFancyBaseClass type
    var constructors = typeof(SuperFancyBaseClass).GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

    // Loop through each constructor
    foreach (var constructor in constructors)
    {
        // Get all of the parameters from the constructor
        var parameters = constructor.GetParameters();

        // Get all of the types from parameters
        var parameterTypes = parameters.Select(p => p.ParameterType).ToArray();

        // Build the new constructor on the new type we are creating
        var newConstructor = typeBuilder.DefineConstructor(MethodAttributes.Public, constructor.CallingConvention, parameterTypes);

        // Loop through each parameter in the constructor
        for (var i = 0; i < parameters.Length; ++i)
        {
            var parameter = parameters[i];
            // Define the parameter
            var parameterBuilder = newConstructor.DefineParameter(i + 1, parameter.Attributes, parameter.Name);
        }

        // Get the IL generator from the new constructor we defined earlier
        var emitter = newConstructor.GetILGenerator();
        emitter.Emit(OpCodes.Nop);

        // Load `this` and call base constructor with arguments
        emitter.Emit(OpCodes.Ldarg_0);
        for (var i = 1; i <= parameters.Length; ++i)
        {
            emitter.Emit(OpCodes.Ldarg, i);
        }
        emitter.Emit(OpCodes.Call, constructor);

        emitter.Emit(OpCodes.Ret);
    }

   return typeBuilder.CreateTypeInfo();
}
Enter fullscreen mode Exit fullscreen mode

And there you have it! We just defined our own type, implemented an interface, inherited a base class, defined a constructor and called the base constructor all at runtime.

Click here for the source code in action

TLDR: I needed to implement an interface to an existing class at runtime, which required some fun with dynamic assemblies and IL generation

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
atleastitry
Matt Hope

Posted on May 31, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About