Leveraging Member Initializers

So a few weeks back there was a  post on the Genome TeamBlog which included a link to one of my  lambda abuse posts from last year.

At any rate - the problem they faced was that with code like this:

DataContext Context = new DataContext();

string connStr = "";

DataDomainSchema schema = DataDomainSchema.LoadFrom("SomeMappingFile");

schema.CreateDbSchema(connStr);

DataDomain dd = new DataDomain(schema, connStr);

using (Context.Push(ShortRunningTransactionContext.Create()))
{
Customer tt = dd.New();
tt.Name = "TechTalk";

RootProject tt_hk = dd.New();
tt_hk.Name = "Housekeeping";

ChildProject tt_hk_hol = dd.New();
tt_hk_hol.Name = "Holiday";
tt_hk.ChildProjects.Add(tt_hk_hol);

ChildProject tt_hk_ill = dd.New();
tt_hk_ill.Name = "Illness";

tt_hk.ChildProjects.Add(tt_hk_ill);

tt.RootProjects.Add(tt_hk);

RootProject tt_g = dd.New();
tt_g.Name = "Genome";

ChildProject tt_g_dev = dd.New();
tt_g_dev.Name = "Development";
tt_g.ChildProjects.Add(tt_g_dev);

ChildProject tt_g_mnt = dd.New();
tt_g_mnt.Name = "Maintenance";
tt_g.ChildProjects.Add(tt_g_mnt);
tt.RootProjects.Add(tt_g);

Context.CommitCurrent();
}


You ended up with a very flat member initialization structure plagued with:
  • Having to explicitly name child instances being added to collections - there's a lot of unnecessary noise.
  • Where you can't easily see the structure i.e. it's not visually hierarchical, so at a glance you're not sure just what the structure is compared to say looking at an xml document with nested elements where it's quite obvious.


At any rate, the guys at the Genome project attempted to overcome this using the nested lambdas (what I coined a "DSL" at the time, though It's a terrible and inaccurate term for what's effectively just a bit of a "trick" relying on side effects of evaluation) - it didn't go so well though because of course at first glance the syntax look strongly typed, the reality is it's anything but, and refactoring tools just aren't going to do things like renaming of keys in the hash style syntax i.e. key => value, because the key is just a Lambda parameter.

But all is not lost - of course with C# 3.0 we already have a great syntax for doing this kind of hierarchical initialization, say for this set of types:

public class BlogPost
{
private readonly List _tags = new List();
private readonly BlogUser _createdBy = new BlogUser();

public string Title { get; set; }

public string Body { get; set; }

public List Tags
{
get { return _tags; }
}

public BlogUser CreatedBy
{
get { return _createdBy; }
}

public BlogUser LastEditedBy { get; set; }
}

public class BlogUser
{
public int Age { get; set; }
public string Name { get; set; }
}


We could do something like this to initialize an instance of BlogPost:
BlogPost post = new BlogPost()
{
Title = "Post on Lambdas",
Body = "This is a post...",
Tags =
{
".Net",
"Lambda",
"C#3.0"
},
CreatedBy =
{
Name = "Jane Doe",
Age = 35
},
LastEditedBy = new BlogUser()
{
Name = "Joe Bloggs",
Age = 25
}
};

But the catch for the Genome guys is that it looks like they need to construct their entities using their DataDomain class ...  I don't know about how there product works but I can only assume it's either to get a transparent proxy for change tracking purposes or to enlist it into the current session etc. (though if it's just to enlist the entity I can't see why they need to bother with getting the DataDomain to create a new instance, surely they could manually enlist it).

At any rate that's irrelevant :)

So I got to thinking that of course member initialization is one of the Lambda-friendly things we can do because it's expressed in a single statement - so we can happily take the above code snippet and express it like so:

BlogPost post = evaluator.Create(() => new BlogPost()
{
Title = "Post on Lambdas",
Body = "This is a post...",
Tags =
{
".Net",
"Lambda",
"C#3.0"
},
CreatedBy =
{
Name = "Jane Doe",
Age = 35
},
LastEditedBy = new BlogUser()
{
Name = "Joe Bloggs",
Age = 25
}
});

Where evaluator is an instance of a class I wrote called ServiceInjectionEvaluator ... the Create method (as you can probably guess) has the following signature:
public T Create(Expression<>> expression)

The service injection evaluator just relies on being configured with an  IServiceProvider capable of resolving instances of types... at this point we just unwind the expression, substituting our own instance activation mechanism wherever we stumble upon a  NewExpression and walking through the expressions executing each bit as required - though it makes the assumption that you're going to have either a New or MemberInit expression at the top level of the Lambda, otherwise we just compile the whole thing and throw it back without any changes (because I don't want to bother writing code to visit the other types of expression node).
public class ServiceInjectionEvaluator
{
private readonly IServiceProvider _serviceProvider;

public ServiceInjectionEvaluator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

public T Create(Expression<>> expression)
{
switch (expression.Body.NodeType)
{
case ExpressionType.New:
case ExpressionType.MemberInit:
return (T)EvaluateExpression(expression.Body);
default:
return expression.Compile().Invoke();
}
}

private object GetInstanceWithInit(MemberInitExpression expression)
{
object instance = GetInstance(expression.NewExpression);
foreach (MemberBinding binding in expression.Bindings)
{
ApplyBinding(instance, binding);
}
return instance;
}

private void ApplyBinding(object instance, MemberBinding binding)
{
switch (binding.BindingType)
{
case MemberBindingType.Assignment:
ApplyAssignmentBinding(instance, (MemberAssignment)binding);
break;
case MemberBindingType.ListBinding:
ApplyListBinding(instance, (MemberListBinding)binding);
break;
case MemberBindingType.MemberBinding:
ApplyMemberBinding(instance, (MemberMemberBinding)binding);
break;
default:
throw new NotImplementedException();
}
}

private void ApplyMemberBinding(object instance, MemberMemberBinding binding)
{
PropertyInfo property = (PropertyInfo)binding.Member;
object memberValue = property.GetValue(instance, null);
foreach (MemberBinding childBinding in binding.Bindings)
{
ApplyBinding(memberValue, childBinding);
}
}

private void ApplyListBinding(object instance, MemberListBinding binding)
{
object list = ((PropertyInfo)binding.Member).GetValue(instance, null);

foreach (ElementInit elementInit in binding.Initializers)
{
Delegate compiled = Expression.Lambda(Expression.NewArrayInit(typeof(object), elementInit.Arguments.ToArray())).Compile();
object[] arguments = (object[])compiled.DynamicInvoke();
elementInit.AddMethod.Invoke(list, arguments);
}
}

private void ApplyAssignmentBinding(object instance, MemberAssignment assignment)
{
object value = EvaluateExpression(assignment.Expression);
PropertyInfo info = (PropertyInfo)assignment.Member;
info.SetValue(instance, value, null);
}

private object EvaluateExpression(Expression expression)
{
switch (expression.NodeType)
{
case ExpressionType.New:
return GetInstance((NewExpression) expression);
case ExpressionType.MemberInit:
return GetInstanceWithInit((MemberInitExpression) expression);
default:
return Expression.Lambda(expression).Compile().DynamicInvoke();
}
}

private object GetInstance(NewExpression expression)
{
return _serviceProvider.GetService(expression.Type);
}
}


The only thing left is to then either provide an existing instance of IServiceProvider (i.e. the Windsor container) or creating an adaptor
public class DataDomainServiceProvider : IServiceProvider
{
private readonly DataDomain _domain;
private static readonly MethodInfo member = typeof(DataDomain).GetMethod("New");

public DataDomainServiceProvider(DataDomain domain)
{
_domain = domain;
}

public object GetService(Type serviceType)
{
return member.MakeGenericMethod(serviceType).Invoke(_domain, null);
}
}


There's a few things the enterprising mind can do with substitutions and member initialization I can think of, especially around IoC - anyone else have some ideas or thoughts on using/abusing them?
Written on February 20, 2008