December 2, 2025

Create Types on Demand and Cecilifier

This post is part of C# Advent Calendar 2025. Visit it for all the awesome upcoming posts!

The traditional C# Advent is here. Usually I give some daily and practical advice. Like testing tips [1] or some outsider view approach to C# [2] here. This time its more about fun with very niche applications.

Provide 'Just In Time' Types & Code?

In dotNet there are facilities to create types at Runtime. This exists of two things:

  • A callback that is called when a type isn’t found.

  • Loading and generating Assemblies at runtime.

Be the Type-Force with you
Figure 1. Be the Type-Force with you

The AppDomain.CurrentDomain.TypeResolve callback that you can register. It is called every time a type is not found:

AppDomain.CurrentDomain.TypeResolve += (sender, args) =>
{
    Console.Out.WriteLine($"Oh boy, oh boy: The type: {args.Name} is missing");
    // But I couldn't find it as well
    return null;
};

var myType = Type.GetType("Gamlor.Shinanigans.HelloWorld");
var myInstance = myType.GetConstructors()[0].Invoke([]);
Console.Out.WriteLine($"Say hi from my type: {myInstance}");

This will crash, as we didn’t provide the assembly where the type could live, and the type lookup will return null

Oh boy, oh boy: The type: Gamlor.Shinanigans.HelloWorld is missing
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at Program.<Main>$(String[] args) in /home/roman/dev/temp/RiderProjects/ConsoleApp1/ConsoleApp1/Program.cs:line 18

Then next step is provide some code. We could load an assembly from disk with the missing type. Or we can generate code in a dynamic assembly:

// The AssemblyBuilder allows you to dynamically generate an Assembly
var ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("MyDyanicAssembly"), AssemblyBuilderAccess.Run);
var module = ab.DefineDynamicModule("MainModule");

// In the Callback, we generate the missing type
AppDomain.CurrentDomain.TypeResolve += (sender, args) =>
{
    var typeDef = module.DefineType(args.Name, TypeAttributes.Public | TypeAttributes.Class);

    var toStringMethod = typeDef.DefineMethod("ToString",
        MethodAttributes.Private | MethodAttributes.HideBySig |
        MethodAttributes.NewSlot | MethodAttributes.Virtual,
        typeof(string),
        Type.EmptyTypes);
    // Generate the code
    ILGenerator il = toStringMethod.GetILGenerator();
    il.Emit(OpCodes.Ldstr, "Hello World from: " + args.Name);
    il.Emit(OpCodes.Ret);

    // Override the .ToString method and create the type
    typeDef.DefineMethodOverride(toStringMethod, typeof(object).GetMethod("ToString"));
    typeDef.CreateType();

    // Return our AssemblyBuilder: It is a sub-type of Assembly
    return ab;
};

{
    // As for a type, and it magically is generated
    var myType = Type.GetType("Gamlor.Shinanigans.MyFirstType");
    var myInstance = myType.GetConstructors()[0].Invoke([]);
    Console.Out.WriteLine($"ToString: {myInstance}");
}
{
    // And one more
    var myType = Type.GetType("Gamlor.Shinanigans.More.Types");
    var myInstance = myType.GetConstructors()[0].Invoke([]);
    Console.Out.WriteLine($"ToString: {myInstance}");
}

When running, the new types are created and have the ToString method overriden:

ToString: Hello World from: Gamlor.Shinanigans.MyFirstType
ToString: Hello World from: Gamlor.Shinanigans.More.Types

Building the IL: cecilifier.me

The hard part for me is always the IL. I don’t know by hard and I need to use it very rarely.

Luckily, there is https://cecilifier.me! Thanks to Adriano.

On this website you can write some C# on the left. On the right it will generate code for the Mono.Cecil library. If you use Mono.Cecil, you can use it directly. However, even without Cecil, you can squint a bit and translate it the built-in libraries.

Update Adriano (the creator of cecilifier.me) told me that if you only need the IL and not the Mono.Cecil specifics, then https://sharplab.io/ and https://lab.razor.fyi/ are great alternatives.

Cecilifier Example
Figure 2. Cecilifier Example

For my ToString example:

  1. I selected an example ToString method.

  2. Then I saw what binding flags and what IL instructions I needed.

Babelfish for IL
Figure 3. Babelfish for IL

Use-cases and Summary

What are the actual use cases for these techniques? It is mostly some kind of frame-work code that will use these capabilities. Like a framework that adds instrumentation/interception at runtime. Another use case is some kind of interpreter / compiler of a DSL.

Summary: You can load and generate IL code just in time in dotNet. And https://cecilifier.me helps you to write code that writes IL.

Tags: C# Development