I recently wrote a rant on here about Dependency Injection and Inversion of Control containers. This might leave you to believe I’m totally against IoC. In fact, I’m not. There is a time and a place for this pattern, I just feel that it’s overused, especially in designs where the only purpose it’s serving is to allow for unit testing.
However, right now I’ve got a use for it. The details about why aren’t that relevant, so I’m not going to discuss it here. However, my needs are isolated and still want to take a pragmatic approach to the usage. I don’t expect to need runtime configuration, though I don’t want to strictly rule it out, either. So, I’ve been researching the available IoC containers for the CLR. Most of them seem to be sledgehammer solutions, when I need a claw hammer. The one that seemed to come the closest to what I’d want is Autofac, but there’s a lot of features there that I don’t necessarily need either. What I’d really like is to code to an extremely light weight interface, and provide a really simple default implementation, allowing for the ability to use any of the other containers if and/or when my needs grow.
Did you know the BCL contains such an interface? More importantly, it provides a very simple container as well. If you’ve done much in WPF, you’ve actually probably used the interface already. What am I talking about?
This simple interface provides a single method: object GetService(Type serviceType). That’s it. And that’s all you need to get services from an IoC container. Oh, I also mentioned that the BCL provides a container as well. There’s both an IServiceContainer interface and a ServiceContainer class. This container also happens to be very similar to the container in Autofac, utilizing delegate callbacks as the mechanism for specifying how service instances should be retrieved. This is very light weight, very fast, and very flexible. I’ll give some examples on how to use this in a bit. First, though, I’d like to discuss some "short comings" of these interfaces.
Jeremy Miller had a post recently, where he ask "Is it time to make IoC/DI tooling part of the BCL?" Jeremy is a very sharp guy. He created StructureMap, another one of the IoC containers available on the CLR. I know he has to be familiar with these APIs, so it’s not readily obvious from the post what he thinks is actually lacking in the BCL. However, some of the comments to this post do point out some of the problems I do see with the existing APIs. First, these APIs were created prior to Generics, and as such they are not as typesafe as one would hope. Second, they can retrieve services only by a Type key, while most of the other containers also allow you to use a key string in addition. There’s a nice suggestion in the comments to extend the IServiceProvider interface with something along the lines of:
public interface IServiceLocator : IServiceProvider
T GetService<T>(string key);
The problem with this is that IServiceContainer extends IServiceProvider, and you can’t change it. Worse, ServiceContainer can’t be changed. You could provide your own container APIs based on the IServiceLocator, but then at best we’re back into the Name Game (TM) for what to call them, so they don’t collide with the current ones.
Another possibility for the generic APIs would be to use an extension method.
public static class ServiceProviderExtensions
public T GetService<T>(this IServiceProvider self)
This works, though I’d like to point out that FxCop/CodeAnalysis will generate a warning about using a method signature that requires an explicit type parameter (CA1004). The MSDN documentation suggests you should never suppress this warning. I’m not sure I can agree fully with the reasoning the documentation gives, i.e. the API is more complicated when you have to specify an explicit type parameter, especially in a circumstance like this. So, I’d be tempted to suppress this and move on. However, I will point out that there is a consideration if you’re rolling your own APIs and you only provide an generic method like this: utilizing the API using reflection is several magnitudes more difficult. In this case, this is just an alternative API that simplifies code. The non-generic API still exists, and can be used in those cases where the generic API is too complicated (i.e. in reflection).
However, no attempts to extend the interface is going to allow you to provide the GetService that takes a key. If you simply must have that, then you must provide extended interfaces and your own container. I’d actually consider doing that in what I’m working on, but I’d have to come up with names.
Well, I rambled enough that I’d better save the "how to use" discussion for another post.