There is another feature that I do not find in the dependency injection frameworks I’ve seen — list injection.

For example, let’s suppose you have a kind of a notification service that sends messages using all possible delivery providers. It makes sense to have a list of these in the service:

public class NotificationService {
    private readonly IDeliveryProvider[] providers;

    public NotificationService(IDeliveryProvider[] providers) {
        this.providers = providers;        
    }

    public void SendAll(INotification[] notifications) {
        var deliveries = from notification in notifications
                         from provider in providers
                         from delivery in provider.GetDeliveries(notification)
                         select delivery;

        this.DeliverAll(deliveries);
    }
}

Now the problem is that there is no out-of-the-box support for such things in Castle, which is somewhat unexpected.

Yes, I can set arrays through configuration. But I never use XML configuration unless there is an extremely important reason for this to be configurable at deployment time, which does not happen very often with IoC containers. Also, documented approach implies that I have to hardcode a list of specific services here, which makes it a change preventer.

So configuration-only solution is out of the question.

Fortunately, in contrast with previous challenges, it is possible to implement this myself. There is a minor problem — Castle feature for getting an array of services (kernel.ResolveServices) is generic-only in RC3. But if I have to choose between hackarounds and using nameless trunk version, I prefer trunk version.

There is a naive version of the resolver:

internal class ListSubDependencyResolver : ISubDependencyResolver {
    private static readonly HashSet<Type> SupportedInterfaces = new HashSet<Type> {
        typeof(IEnumerable<>), typeof(ICollection<>), typeof(IList<>)
    };

    private readonly IKernel kernel;

    public ListSubDependencyResolver(IKernel kernel) {
        this.kernel = kernel;
    }

    public bool CanResolve(CreationContext context, ISubDependencyResolver parentResolver, ComponentModel model, DependencyModel dependency) {
        if (parentResolver.CanResolve(context, parentResolver, model, dependency))
            return true;
        
        var targetType = dependency.TargetType;      
        if (targetType.IsArray)
            return true;
        return targetType.IsInterface
            && targetType.IsGenericType
            && SupportedInterfaces.Contains(targetType.GetGenericTypeDefinition());
    }

    public object Resolve(CreationContext context, ISubDependencyResolver parentResolver, ComponentModel model, DependencyModel dependency) {
        if (parentResolver.CanResolve(context, parentResolver, model, dependency))
            return parentResolver.Resolve(context, parentResolver, model, dependency);
        
        var type = (
            from @interface in dependency.TargetType.GetInterfaces()
            where @interface.IsGenericType 
               && @interface.GetGenericTypeDefinition() == typeof(IEnumerable<>)
            select @interface.GetGenericArguments().First()
        ).Single();

        return this.kernel.ResolveAll(type, new Hashtable());
    }
}

Side note: here we can see two minor problems with API design — first one is the mysterious parentResolver (what should I pass as parentResolver to parentResolver?), second one is that shiny new ResolveAll has no overload for the case when I do not need additionalArguments.

This is a very naive implementation (no checks if I can actually resolve generic parameter), but it works quite well with the original case (as well as with the other IList/ICollection/IEnumerable requirements).

However, there is a logical consistency problem with Singletons — they will get different parameters depending on whether you have resolved them before registering all their dependencies or after. If a Singleton has such dependencies, it can not be Startable, unless you make sure to register all dependencies before it.

I have found almost no information on using lists with DI, so I am not sure how others solve this. But I do not need Startable and all my components are registered at almost same time, so this is not a problem for me for now.

Update: Thanks to Victor Kornov, I have found a this post by Castle developer, which describes the same solution. I have added a proposition to the corresponding Google Group to make it a built-in feature. I prefer it being enabled by default, but I would like to see it either way.

  • http://victorkornov.ru Victor Kornov
  • http://victorkornov.ru Victor Kornov
  • http://blog.ashmind.com ashmind

    Thanks, I wish I have seen it before.
    It is almost the same solution I am providing, but without the support for IEnumerable/IList/ICollection dependencies.
    There is still a question why it is not in Castle itself.

  • http://blog.ashmind.com Andrey Shchekin

    Thanks, I wish I have seen it before.
    It is almost the same solution I am providing, but without the support for IEnumerable/IList/ICollection dependencies.
    There is still a question why it is not in Castle itself.

  • Weex

    hammett Says:
    April 4th, 2008 at 3:23 pm

    The problem of making this behavior standard is the possible backward compatibility ghost. If you propose this on the devel list, we could certainly run a vote and decide it.

  • http://victorkornov.ru Weex

    hammett Says:
    April 4th, 2008 at 3:23 pm

    The problem of making this behavior standard is the possible backward compatibility ghost. If you propose this on the devel list, we could certainly run a vote and decide it.

  • http://blog.ashmind.com ashmind

    Thanks, missed that. I have posted to the dev list, so we'll see.

  • http://blog.ashmind.com Andrey Shchekin

    Thanks, missed that. I have posted to the dev list, so we’ll see.

  • http://blog.ashmind.com Andrey Shchekin

    Thanks, missed that. I have posted to the dev list, so we'll see.

  • Weex

    hammett Says:
    April 4th, 2008 at 3:23 pm

    The problem of making this behavior standard is the possible backward compatibility ghost. If you propose this on the devel list, we could certainly run a vote and decide it.

  • http://blog.ashmind.com Andrey Shchekin

    Thanks, missed that. I have posted to the dev list, so we'll see.

  • Pingback: Comparing .NET DI (IoC) Frameworks, Part 2 » Blog Archive » I Think It’s Interesting()

  • janus007

    Old post… :)

    But StructureMap can easily handle such lists!