Saturday, 28 March 2009

Building a Reusable Builder: An internal DSL in C#

Sitting and enjoying the OpenSpace Coding Day at Conchango, and in particular enjoying Ian Coopers talk on Internal DSLs in C# I came to thinking how it might be quite simple to create a reusable builder object.

I’ve done some posts on this sort of thing before, and have spent quite a bit of time recently at work constructing the beginnings of a Language Workbench (I say beginnings as I’m following YAGNI and there’s only one DSL that uses it so far).

Anyway here’s what I’ve just sketched out at the back of the class.

public class Builder<T>
{
    public BuildingInProgress<T> With(T targetObject)
    {
        return new BuildingInProgress<T>(targetObject);
    }
}

public class BuildingInProgress<T>
{
    private readonly T _target;

    public BuildingInProgress(T targetObject)
    {
        _target = targetObject;
    }

    public BuildingInProgress<T> Set<TValue>(Expression<Func<T, TValue>> property, TValue value)
    {
        PropertyInfo info = ExpressionHelpers.GetPropertyInfo(property);
        info.SetValue(_target, value, null);
        return this;
    }
}

public static class ExpressionHelpers
{
    public static string GetMemberName<TSubject, TMember>(Expression<Func<TSubject, TMember>> subjectMember)
    {
        PropertyInfo propertyInfo = getMemberExpressionMember(subjectMember.Body);
        return propertyInfo.Name;
    }

    public static PropertyInfo GetPropertyInfo<TType,TMember>(Expression<Func<TType,TMember>> property)
    {
        return getMemberExpressionMember(property.Body);
    }

    private static PropertyInfo getMemberExpressionMember(Expression expression)
    {
        if (expression == null) return null;

        switch (expression.NodeType)
        {
            case ExpressionType.MemberAccess:
                {
                    MemberExpression memberExpression = (MemberExpression)expression;
                    return (PropertyInfo)memberExpression.Member;
                }
            default:
                throw new InvalidOperationException(string.Format("The Expression Type was not expected: {0}", expression.NodeType));
        }
    }
}

And that’s basically as far as I have got in ten minutes (whilst paying attention as well of course;). Obviously it has weaknesses, the static reflection would need much building out to handle more than simple MemberExpressions for example.

Here is a really trivial example of its intended use:

[TestFixture]
public class BuilderFixture
{
    [Test]
    public void Where_SetIsCalled_Then_AValueShouldBeSet()
    {
        const string testValue = "testValue";
        const int otherTestValue = 12;
        Foo foo = new Foo();
        Builder<Foo> fooBuilder = new Builder<Foo>();
        fooBuilder.With(foo)
            .Set(f => f.Bar, testValue)
            .Set(f => f.Baz, otherTestValue);
        Assert.AreEqual(testValue,foo.Bar);
        Assert.AreEqual(otherTestValue, foo.Baz);
    }
}

public class Foo
{
    public string Bar { get; set; }
    public int Baz { get; set; }
}