ThoughtWorks recruiting event in New York
27 minutes ago
[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
[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)