When Josh replied that injecting dependencies with constructor arguments doesn't really solve the problem of dependency creation, I was tempted to reply by enumerating all the .NET dependency injection frameworks that exist for exactly this purpose.
But then I realized that Josh had demonstrated the Service Locator pattern without using any framework. Instead, his article has a ServiceContainer class of about 30 lines. Service Locator has many disadvantages, but apparently it can be quite lightweight!
This then lead me to wonder if the same could be done for creating a dependency injection framework. Ayende has actually already demonstrated that you can create a primitive one in 15 lines, but I was thinking of something that could be used with a more friendly Ninject-esque syntax like this:
var container = new Container(); container.Bind<App, App>(); container.Bind<IFoo, Foo>(); container.Bind<IBar, Bar>(); var app = container.Pull<App>(); app.Run();
As it turns out, implementing a bare bones container which can do that is really not that hard. It also has the advantage that it takes care of the dependencies of the dependencies etcetera, something which Josh's sample doesn't seem to do. (Disclaimer: I didn't really test this for anything but the plain vanilla use case, no error conditions were considered.)
public class Container { private readonly Dictionary<Type, Type> contractToClassMap = new Dictionary<Type, Type>(); private readonly Dictionary<Type, object> contractToInstanceMap = new Dictionary<Type, object>(); public void Bind<TContract, TClass>() where TClass : class, TContract { this.contractToClassMap[typeof(TContract)] = typeof(TClass); } public TContract Pull<TContract>() { return (TContract)Pull(typeof(TContract)); } public object Pull(Type contract) { object instance; this.contractToInstanceMap.TryGetValue(contract, out instance); if (instance == null) { var constructor = contractToClassMap[contract].GetConstructors()[0]; var args = from parameter in constructor.GetParameters() select Pull(parameter.ParameterType); instance = constructor.Invoke(args.ToArray()); this.contractToInstanceMap[contract] = instance; } return instance; } }
1 comment:
Nice!
I think there is one easy optimization towards reduced memory consumption: Instead of using Type objects as keys into the two dictionaries, use their corresponding RuntimeTypeHandles. These light-weight tokens should be a lot cheaper to keep around, which might become important when your container stays around for a long time, or when you have lots of registrations.
Perhaps there's also a way to get rid of Type objects being used as values in the former dictionary, and instead store only what is actually required, i.e. information about the constructors to be invoked.
Post a Comment