Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a simple way to register static closures with Castle Windsor?

I've been experimenting with using named delegates instead of single-method interfaces. This has some advantages for code size, as we can go from (some linebreaks removed so as not to overstate the case):

public interface IProductSource
{
    IEnumerable<Product> GetProducts();
}
public class DataContextProductSource : IProductSource
{
    private readonly DataContext _DataContext;
    public DataContextProductSource(DataContext dataContext)
    {
        if (dataContext == null) throw new ArgumentNullException("dataContext");
        _DataContext = dataContext;
    }
    public IEnumerable<Product> GetProducts()
    {
        return _DataContext.Products.AsEnumerable();
    }
}

to:

public delegate IEnumerable<Product> DGetProducts();
public static class DataContextFunctions
{
    public DGetProducts GetProducts(DataContext dataContext)
    {
        if (dataContext == null) throw new ArgumentNullException("dataContext");
        return () => dataContext.Products.AsEnumerable();
    }
}

This is basically taking advantage of the fact that once you go far enough with dependency injection, a lot of classes become little more than closures. Those classes can be replaced with functions that return lambdas. Entire sets of related functions (that don't need to encapsulate any mutable state, but would have been expressed using classes in "standard" dependency injection), can then be rolled up into a static class (or "module" in VB parlance).

This is all well and good, but I'm having trouble finding the best way to register these static methods with Castle Windsor. Methods with no dependencies are easy:

Component.For<DGetIntegers>().Instance(Integers.GetOneToTen)

But our DataContextFunctions.GetProducts from above has some dependencies. The best way I've found to register this is:

Component.For<DGetProducts>().UsingFactoryMethod(
    kernel => DataContextFunctions.GetProducts(kernel.Resolve<DataContext>())

This can get quite verbose, and obviously having to ask the kernel directly for each dependency kind of defeats the point a bit. It seems to me that all the static information that a container should need is available, so it should be possible to do this.

The question is, does Castle Windsor (or any other container) have a simple way to do this that I've missed, or are there technical problems that arise, or is it just too niche a use case to have been included?

like image 841
ninjeff Avatar asked Aug 20 '11 09:08

ninjeff


2 Answers

Short answer

You're trying to make a DI Container (Castle Windsor) perform function composition, but it's actually targeted at object composition. That's just going to give you a lot of friction. My guess is that you'll get the same experience with other containers.

DI Containers are designed around object-oriented concepts - particularly SOLID. They work very well with these principles because they were designed with inherent understand of such things as Constructor Injection and Auto-wiring.

There's nothing wrong with a more functional approach, but I've yet to see a DI Container built around function composition instead of object composition.

Long answer

The use of delegates as a general principle for DI tends to be problematic in statically typed languages (at least in .NET) for a couple of reasons. Conceptually there's nothing wrong with this approach since a delegate can be considered as an anonymous Role Interface. However, it tends to become unwieldy because of type ambiguity.

The most common approach I normally see is to use the BCL's built-in delegates, such as Func<T>, Action<T> and so on. However, you may have many different consumers that rely on Func<string> in which case things become ambiguous - just because a consumer requires a Func<string> doesn't mean that they require the delegate in the same role. Although it's mechanically possibly to use DI with delegates, what happens is that delegates hide application roles. Roles disappear, leaving only mechanics.

You could then, as suggested in the OP define custom delegates for each role, as suggested by this delegate:

public delegate IEnumerable<Product> DGetProducts();

However, if you take that route, then nothing is gained. A 'role delegate' must still be defined for each role. Contrast that to defining a similar interface and it should be clear that the only saving is a couple of angle brackets and an explicit method definition:

public interface IProductSource { IEnumerable<Product> GetProducts(); }

That's not a lot of overhead (if any).

You may also want to take a look at this discussion: http://thorstenlorenz.wordpress.com/2011/07/23/dependency-injection-is-dead-long-live-verbs/

like image 66
Mark Seemann Avatar answered Feb 22 '23 03:02

Mark Seemann


That's an interesting approach. I think you could get this working quite easily with a custom activator.

like image 42
Krzysztof Kozmic Avatar answered Feb 22 '23 04:02

Krzysztof Kozmic