This code was originally written and proven out in the related Marten and described in a post titled Using Roslyn for Runtime Code Generation in Marten. This code was ripped out of Marten itself, but it’s happily running now in Lamar a couple years later.
As some of you know, I’ve been working on a new library called Lamar that I mean to be the next generation replacement for the venerable, but creaky StructureMap library. Besides the IoC Container support though, Lamar also provides some tooling and a model to generate and compile code at runtime, then ultimately load and use the newly generated types.
If all you want to do is take some C# code and compile that in memory to a new, in memory assembly, you can use
the Lamar.Compilation.AssemblyGenerator
class.
Let’s say that you have a simple interface in your system like this:
public interface IOperation { int Calculate(int one, int two); }
Next, let’s use AssemblyGenerator
to compile code with a custom implementation of IOperation
that we’ve generated
in code:
[Fact] public void generate_code_on_the_fly() { var generator = new AssemblyGenerator(); // This is necessary for the compilation to succeed // It's exactly the equivalent of adding references // to your project generator.ReferenceAssembly(typeof(Console).Assembly); generator.ReferenceAssembly(typeof(IOperation).Assembly); // Compile and generate a new .Net Assembly object // in memory var assembly = generator.Generate(@" using Lamar.Testing.Samples; namespace Generated { public class AddOperator : IOperation { public int Calculate(int one, int two) { return one + two; } } } "); // Find the new type we generated up above var type = assembly.GetExportedTypes().Single(); // Use Activator.CreateInstance() to build an object // instance of our new class, and cast it to the // IOperation interface var operation = (IOperation)Activator.CreateInstance(type); // Use our new type var result = operation.Calculate(1, 2); result.ShouldBe(3); }
There’s only a couple things going on in the code above:
- I added an assembly reference for the .Net assembly that holds the
IOperation
interface - I passed a string to the
GenerateCode()
method, which successfully compiles my code and hands me back a .Net Assembly object - Load the newly generated type from the new Assembly
- Use the new
IOperation
If you’re not perfectly keen on doing brute force string manipulation to generate your code, you can
also use Lamar’s built in ISourceWriter to generate some of the code for you with
all its code generation utilities:
[Fact] public void generate_code_on_the_fly_using_source_writer() { var generator = new AssemblyGenerator(); // This is necessary for the compilation to succeed // It's exactly the equivalent of adding references // to your project generator.ReferenceAssembly(typeof(Console).Assembly); generator.ReferenceAssembly(typeof(IOperation).Assembly); var assembly = generator.Generate(x => { x.Namespace("Generated"); x.StartClass("AddOperator", typeof(IOperation)); x.Write("BLOCK:public int Calculate(int one, int two)"); x.Write("return one + two;"); x.FinishBlock(); // Finish the method x.FinishBlock(); // Finish the class x.FinishBlock(); // Finish the namespace }); var type = assembly.GetExportedTypes().Single(); var operation = (IOperation)Activator.CreateInstance(type); var result = operation.Calculate(1, 2); result.ShouldBe(3); }
In Part 2, I’ll talk about the “frames” model that’s heavily used within Jasper (shown in this post).
Lamar is really cool – I’ve been enjoying your posts on its development.
I’m wondering why your API for generation is using explicit start/finish calls instead of automatic scopes via `using`, like this:
var assembly = generator.Generate(x =>
{
using (x.Namespace(“Generated”))
{
using( x.Class(“AddOperator”, typeof(IOperation)))
{
using (x.Method(“public int Calculate(int one, int two)”))
{
x.Write(“return one + two;”);
} // Finish the method
} // Finish the class
} // Finish the namespace
});
I like the way this creates a ScottGu style “pit of success” where every scope has to be explicitly closed, no chance for a mismatch.
I’m not super wild about it for the same reason you said, but the issue with what you said up there is disambiguating your syntax from just straight up “using” in the generated code. There is some helpers on ISourceWriter that do a using with continuation passing style, but I don’t know if that would really be popular.
How about:
“`
using (writer.StartBlock(“some code here”)
{
// more usages of ISourceWriter
}
“`
and that automatically sticks in the open & closed brackets, and also does the automatic increment and decrement?
This is very cool. I like the idea of the using statements, I would definitely use that approach myself.
If anybody wants to play with this in LinqPad like I did, be sure to set the “Run each query in its own process” to False. It will still display some warnings but should work.