In-Memory Code Generation With .NET – Loading

The previous articles from this post series have discussed how to generate .NET types at runtime and how to get .NET assemblies that contain these types. This article shows different methods of finding the generated types in a given assembly and creating instances of these types at runtime.

Reflection

The most basic method of using the type we have just created is the utilization of System.Reflection. Since we have generated an assembly, all we need to do is to search all (exported) types in that assembly for a type that implements the interface or base class we are looking for. If you happen not to be familiar with Reflection, here’s an article that sums up all you need to get going.

var csharpCode = @"
    using System;
    using System.ComponentModel.Composition;

    namespace ThisDemo
    {
        public class YouSayHello : ISayHello
        {
            public void SayHello()
            {
                Console.WriteLine(""Hello"");
            }
        }
    }";

// Compile the code into an in-memory assembly.
var generatedAssembly = Compile(csharpCode);

// Get the first type that implements the required interface from the generated assembly.
var generatedType = generatedAssembly.ExportedTypes.First(t => typeof(ISayHello).IsAssignableFrom(t));

// Create an instance of the type.
var sayHello = (ISayHello)Activator.CreateInstance(generatedType);

// Use the instance.
sayHello.SayHello();

The advantage of using this method is that no additional (3rd party) components are required. Only a few lines of code are required to achieve the goal. Also, we control every aspect of the process since we are using a low-level functionality of the .NET framework. The disadvantage, however, is that we must control every aspect of the process since we are using a low-level functionality of the .NET framework.

Dependency Injection

It’s not without good reason that dependency injection (DI) or inversion of control (IoC) is a pattern that is being more and more adopted. I won’t go into details about what DI\IoC is or what the advantages of DI\IoC are, just into how to instantiate and use our generated types in a DI\IoC scenario.

class CodeRunner
{
    ISayHello sayHello;

    public CodeRunnter(ISayHello sayHello)
    {
        this.sayHello = sayHello;
    }

    public void Run()
    {
        this.sayHello.SayHello();
    }
}

var csharpCode = @"
    using System;

    namespace ThisDemo
    {
        public class YouSayHello : ISayHello
        {
            public void SayHello()
            {
                Console.WriteLine(""Hello""
            }
        }
    }";

// Compile the code into an in-memory assembly.
var generatedAssembly = Compile(csharpCode);

// Get the first type that implements the required interface from the generated assembly.
var generatedType = generatedAssembly.ExportedTypes.First(t => typeof(ISayHello).IsAssignableFrom(t));

// Register the type with the DI container.
container.Register<ISayHello>(generatedType);

// Use DI container to build an object graph.
// The object graph will contain an instance of the generated type.
// Assuming that the type CodeRunner has been registered with the container earlier.
var codeRunner = container.Resolve<CodeRunner>();

// Use the object graph, eventually calling the generated type.
codeRunner.Run();

The code above shows that we still need to find the type in the generated assembly ourselves before registering it with the DI container. There might be DI frameworks that are able to search assemblies for types themselves (such as Autofac, if I remember correctly), but even then we’ll need to tell the DI container what type of types to search for (i.e. by defining a base class or interface or by providing a filter delegate).

The DI\IoC pattern is best suited for scenarios where types are added once but not changed for the rest of the application’s life time.

Managed Extensibility Framework

Enter MEF, Microsoft’s Managed Extensibility Framework. There are lots of articles on the internet about the difference between MEF and DI frameworks and about the pros and cons of choosing either one, so I won’t get into details here. But I do want to point out that MEF plays very well in scenarios with dynamic assemblies, since MEF is made for dynamic type and dependency discovery at runtime. So let’s look at some code:

using System.ComponentModel.Composition;

class CodeRunner
{
    [Import]
    ISayHello sayHello;

    public void Run()
    {
        this.sayHello.SayHello();
    }
}

var csharpCode = @"
    using System;
    using System.ComponentModel.Composition;

    namespace ThisDemo
    {
        [Export(typeof(ISayHello))]
        public class YouSayHello : ISayHello
        {
            public void SayHello()
            {
                Console.WriteLine(""Hello"");
            }
        }
    }";

// Compile the code into an in-memory assembly.
var generatedAssembly = Compile(csharpCode);

// Initialize MEF to search in the generated assembly.
var catalog = new AssemblyCatalog(generatedAssembly);
var container = new CompositionContainer(catalog);

// Instantiate the root of the object graph.
var codeRunner = new MefCodeRunner();

// Use MEF container to build-up the object graph.
// The object graph will contain an instance of the generated type.
container.ComposeParts(codeRunner);

// Use the object graph, eventually calling the generated type.
codeRunner.Run();

Notice the Export attribute in the C# code above. This is what tells MEF to consider this type. Remember to add this attribute to the generated code or else MEF won’t discover the generated type.

MEF is intended for plugin architectures and supports adding and removing of used types during runtime.

Series Navigation<< In-Memory Code Generation With .NET – Emit & Linq

Freelance full-stack .NET and JS developer and architect. Located near Cologne, Germany.

Leave a Reply

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