Thursday, July 17, 2008

.net: A fluent Facade around System.CodeDom

I've been doing quite a lot of work with System.CodeDom recently (more on exactly what later) and have frankly found the api quite hard to use.

So I've found myself growing a fluent interface facade around the api, for example the following code creates a class with one method that always returns the integer four.

[TestFixture]
public class TestsForWriteUp
{
[Test]
public void testShouldCreateClassWithOneMethod()
{
ClassBuilder builder = new ClassBuilder(new ResolveTypeFromAssemblies());
builder.AddMethod("theMethod", typeof (int)).Number(4).Return();

Compiler compiler = new Compiler("CodeGenTests.dll");
ReturnsAnInt dynamicallyBuildCode = compiler.compile<ReturnsAnInt>(builder);
Assert.AreEqual(4,dynamicallyBuildCode.theMethod());
}
}

public interface ReturnsAnInt
{
int theMethod();
}

So I get hold of a classbuilder, add a method to it, then add the number four and finally return. The reason things look a bit back to front is that the facade is using a stack under the covers, so what I'm really saying is
  • Push a statement (a constant integer of value four) on to the stack and then
  • Pop whatever is at the top of stack and return it.
Finally I take the builder and dynamically compile the contents of the builder into an assembly, in this case in a way that gives me back an instance of a particular type so I can immediately start working with. (I can also save the output to a dll if needed)

The new ResolveTypeFromAssemblies() injected into the ClassBuilder allows it to resolve any types that are used, in this case it just checks the loaded assemblies. The "CodeGenTests.dll" passed into the compiler lets it know the generated code depends upon that assembly (i.e. for the interface ReturnsAnInt).

Actually that first example is not very interesting, how about a slightly more complex example:

[Test]
public void testShouldCreateAddMethod()
{
MethodBuilder methodBuilder = builder.AddMethod("theMethod", typeof (int));
methodBuilder.DeclareParameter(typeof (int), "a").DeclareParameter(typeof (int), "b");
methodBuilder.VarUsage("a").VarUsage("b").BinaryOperator(CodeBinaryOperatorType.Add).Return();

TakesTwoArgs takesTwoArgs = compiler.compile<TakesTwoArgs>(builder);
Assert.AreEqual(3, takesTwoArgs.theMethod(1, 2));
Assert.AreEqual(9, takesTwoArgs.theMethod(4, 5));
}

public interface TakesTwoArgs
{
int theMethod(int a, int b);
}

Still pretty simple, declare a method, add two parameters, sum them and then return the result. Again note the back-to-front invocation style. This is considerably less code than you'd need to accomplish the same thing using the System.CodeDom classes directly.

The next example shows the use of a field, a constructor with an argument and then how to create an instance using that constructor. Hopefully much more terse than CodeDom but still easy to understand.

[Test]
public void testShouldCreateConstructorAndField()
{
string fieldName = "seed";
builder.DeclareVariable(typeof (int), fieldName);
builder.AddConstructor().DeclareParameter(typeof (string), "input")
.FieldUsage(fieldName).VarUsage("input").UnaryOperator(typeof(int),"Parse").VarAssignment();
builder.AddMethod("theMethod", typeof (int)).FieldUsage(fieldName).Return();

Parameter argument = new Parameter(typeof(string),"42");
ReturnsAnInt returnsAnInt = compiler.compile<ReturnsAnInt>(builder,argument);
Assert.AreEqual(42, returnsAnInt.theMethod());
}

Again things look back to front, if you read the line ending in .VarAssignment() from right to left you'll probably find it clearer to see what is happening. The reason for the use of the stack is that it greatly simplifies the underlying code, in effect I'm using Reverse Polish Notation

Finally here is the c# version of the code that is compiled:

namespace GeneratedCode {
using System;
using CodeGen;


public class DynamicallyCompiledCode : CodeGen.CompiledBase, CodeGenTests.TestsForWriteUp.ReturnsAnInt, CodeGen.ICompiled {

public int seed;

public DynamicallyCompiledCode(string input) {
this.seed = int.Parse(input);
}

public virtual int theMethod() {
return this.seed;
}
}
}

I'd be interested in comments, is this something people would find useful? It's grown out of a formula compiler, a dynamic proxy generator and a compiler compiler so is reasonably complete. I'll be posting more about the compiler compiler and how it is using this facade around CodeCom to bootstrap itself soon. (btw I never meant to write the compiler compiler, it just grew out of some work I've been doing around DSL's)

1 Comments:

Blogger Bernardo Heynemann said...

Hey Ian,
I've done quite a lot of work with CodeDom in my Model-View-Presenter framework, around mocks.

I've recently started building a CodeDom fluent as well, but I've never went on to keep building it.

The fluent I was aiming at was:

var definition = codeDomHelper.CreateTypeFor<ISomeContract>()
.WithConstructor(typeof(int), typeof(string), //body as delegate or func?)
.WithMethodOf<int>(
//Body as delegate or as func?
);

var someContract = definition.CreateWith(1, "some string");

I started working on it, but stopped and trashed my project.

This is something I'd be very interested in working in, though.

Cheers,
Bernardo Heynemann
Developer @ ThoughtWorks UK
heynemann@thoughtworks.com

11:40 AM  

Post a Comment

<< Home