Although limited in features, the Simple Injector has simple but flexible way to add features, such as the possibility to work with multiple constructors.
I read Remo Gloor's weblog today. Remo Gloor is a developer on Ninject. In his latest blog post, he describes a new feature of the coming Ninject release concerning constructor selection. The Ninject team is going to add a new feature that allows developers to configure which constructor overload Ninject should pick to create that type.
I'm not a fan of such a feature, because this steers developers away from a clean application design. Types that act as service components, should contain a single definition of the required dependencies. Since constructor injection is the primary way to inject our dependencies, it doesn't make much sense to have multiple (public) constructors, because we end up with multiple definitions of what dependencies a class requires.
The problem with adding features that support corner case scenario’s is that they tend to pollute the framework’s API. While existing users won't notice the slowly increasing API surface (just like frogs tend to stay in the water when it is heated slowly), new users get overwhelmed. Because of this I try to keep the feature set of Simple Injector minimal. Even the feature set of the SimpleInjector.Extensions library is fairly limited.
Funny thing however, is that such a feature, as Remo is adding in the next Ninject version, is already very easy to implement with the Simple Injector, simply by adding an extension method. Here is an example:
Update [2011-12-13]: This code was updated for the Simple Injector v1.3 release.
public static void Register<TService, TImplementation>(
this Container container, IConstructorSelector selector)
where TService : class
{
container.Register<TService>(() => null);
container.ExpressionBuilt += (sender, e) =>
{
if (e.RegisteredServiceType == typeof(TService))
{
var ctor =
selector.GetConstructor(typeof(TImplementation));
var parameters =
from p in ctor.GetParameters()
select container.GetRegistration(p.ParameterType, true)
.BuildExpression();
e.Expression = Expression.New(ctor, parameters);
}
};
}
This extension method does two things. First it registers the service with a 'fake' delegate. This ensures that nobody can exidentally override the implementation, and it ensures the ExpressionBuilt event will get triggered (since it only gets triggered for registered types). Next it hooks a delegate to the ExpressionBuilt event. The ExpressionBuilt event will get raised every time the container builds an Expression object for the creation of a service type, but before this Expression gets compiled to a Func<T> delegate. This allows us to influence how the container creates a type, which is exactly what we're doing here. When the delegate gets called, we first check whether the event gets raised for our TService (otherwise we skip), and if so, we fetch the constructor we wish to use using the supplied IConstructorSelector instance (shown below). Using this constructor we create a NewExpression instance that allows us to create a new instance of the given TImplementation using retrieved constructor, by supplying it the correct set of parameters.
Here is the IConstructorSelector interface with with a convenient default implementation:
public interface IConstructorSelector
{
ConstructorInfo GetConstructor(Type type);
}
public sealed class ConstructorSelector : IConstructorSelector
{
public static readonly IConstructorSelector MostParameters =
new ConstructorSelector(type => type.GetConstructors()
.OrderByDescending(c => c.GetParameters().Length).First());
public static readonly IConstructorSelector LeastParameters =
new ConstructorSelector(type => type.GetConstructors()
.OrderBy(c => c.GetParameters().Length).First());
private readonly Func<Type, ConstructorInfo> selector;
public ConstructorSelector(Func<Type, ConstructorInfo> selector)
{
this.selector = selector;
}
public ConstructorInfo GetConstructor(Type type)
{
return this.selector(type);
}
}
With this in place, we can simply register a multi constructor type as follows:
container.Register<IService, MyServiceImpl>(
ConstructorSelector.MostParameters);
Because we use Expression objects here, retrieving instances like this pure fire, just as it is with the normal Register<TService, TImplementation>() method.
I like to repeat that I discourage anyone to specify multiple constructors on a type, so the usefulness of this feature is limited. Nevertheless did it give me the possibility to showcase the extendibility of the Simple Injector :-).
Happy injecting.
Republished from .NET Junkie [7 clicks].
Read the original version here [1 clicks].