DEV Community

Matt Hope
Matt Hope

Posted on • Originally published at matt-h.dev

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

Top comments (0)