I haven't posted in a while and decided to write a post about one of the little side projects I've worked on recently. I've been lurking around on the ALT.NET mailing list and one of the discussions I take time to keep up on is on Dependency Injection. Something I worked on a few weeks ago has some applications in that area and so I decided to see what others thought.
I was talking with Aaron several months ago about how many problems constructors in static languages cause. Ruby people are fortunate in that their constructors are much more flexible than their C# counterparts. Want something to be a singleton? Just return the same instance from the constructor! Lifecycle management and dependencies can be the responsibility of the constructor itself, rather than a function of a container.
Over the weekend a few weeks ago I wrote a library and MsBuild task that factors constructor calls out of code and into a pluggable factory system. You write code like this:
public class EmailSender
{
public void TellEveryone(string message)
{
TemplateRenderer renderer = new TemplateRenderer();
Console.WriteLine(renderer.Render(message));
}
}
public class EmailSenderFactory : IObjectFactory<EmailSender>
{
public EmailSender Create()
{
return new EmailSender();
}
}
EmailSender emailer = new EmailSender();
emailer.TellEveryone("Hello, World!");
What the MsBuild task will do is this:
- Load the target Assemblies using Mono.Cecil
- Find implementations of IObjectFactory<T> and add them to the FactoryMap.
- Find all instances of new T() for each of those factories and replace them with a call to the Factories.Create<T>(), ignoring calls inside of factories themselves.
- Weave the FactoryMap (just the Factory Type and its Object Type) into the Assembly.
So during runtime we can use the FactoryMap that was serialized in step #4 to create the factories and use them to create instances as they're demanded. It is important to note that your objects need default constructors, even though they may never be called after the code is weaved. One can also replace the factory implementations with IoC backed versions, etc...
Why don't you just create a New.Instance<T> method and use that instead of new T() and save yourself the pain of weaving and having a task and all that, moron?
Good question! I mostly did this as an exercise in code weaving using Cecil. It's a great library, although testing code using it can be a little tedious. I'm tossing around some other projects that I'd love to give a shot and this was a good introduction. You could achieve the same thing using reflection to build your factory map and remember to use New.Instance<T>() instead of the new operator in your code. Granted, the manual approach plasters the concern all over the place and weaving keeps the original code free from that. Which may be an OK trade-off for the simplified process.
Another thing to consider, is that perhaps the weaving approach has its applications elsewhere? Use it specifically for creating domain classes to transparently provide domain services? Another idea I've been tossing around is if you're writing code for platforms where garbage collection isn't as performant (xbox for example) you may be able to transparently add object pooling and other patterns after the fact and not clutter up the original code.
I want to release the code anyway, but I'm tired of uploading zip files. All my future source releases will probably be via Google Code or some public svn repository. Stay tuned.... In the meantime, feel free to opine.