The code is heavily based on Jon Skeet's walkthrough of a IoC container ("IoC container on the fly").
First the code of the IoC container itself:
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace TestComposedIoCContainer { public class CompositionContainer { private readonly Dictionary<Type, Func<object>> providers = new Dictionary<Type, Func<object>>(); private readonly object providersLocker = new object(); public void Bind<TKey, TConcrete>() where TConcrete : TKey { lock (providersLocker) { providers[typeof(TKey)] = () => ResolveByType(typeof(TConcrete)); } } public void Bind<T>(T instance) { lock (providersLocker) { providers[typeof(T)] = () => instance; } } private object ResolveByType(Type type) { var constructors = type.GetConstructors(); if (constructors != null) { ConstructorInfo cInfo; cInfo = constructors.Count() == 1 ? constructors.Single() : constructors.Where(c => c.GetCustomAttributes(typeof(ImportConstructorAttribute), false).Length > 0).FirstOrDefault(); if (cInfo == null) throw new Exception(GetUsageMessage(type)); var arguments = cInfo.GetParameters().Select(p => Resolve(p.ParameterType)).ToArray(); return cInfo.Invoke(arguments); } else { var instanceField = type.GetField("Instance"); if (instanceField != null) return instanceField.GetValue(null); else throw new Exception(GetUsageMessage(type)); } } private static string GetUsageMessage(Type type) { return "Could not resolve a type implementing " + type.Name + " - it must be registered through Bind to the composition container and either contain a single constructor or one constructor " + "decorated with ImportContructor attribute or a field named Instance"; } internal TKey Resolve<TKey>() { return (TKey)Resolve(typeof(TKey)); } internal object Resolve(Type type) { Func<object> provider; if (providers.TryGetValue(type, out provider)) return provider(); else return ResolveByType(type); } } }The IoC container contains a dictionary which has got a Type as the Key, which is the usually either the interface or the concrete type to register through the Bind calls to the container, and a function expression that returns either an instance or resolves an instance through the container. When resolving a type, it can be multiple constructors in the class. I have extended Jon Skeet's code a bit, and by decorating the constructor by a ImportConstructor attribute, it is possible to specify which constructor is the constructor that should be the inversion of control constructor. Pass in all dependencies that must be resolved in that constructor. If the importing type or "part" to use a phrase from MEF, has no constructors that should be used, a field called "Instance" can be used. This is to support singleton patterns or similar. I have chosen to add locking when performing binds, to make this thread safe. Resolving instances is not made thread safe, as this can give very much locking. Usually a composition container is anyways set up in a single thread through registering bindings and then multiple threads will possibly access the IoC container. It is possible to put the CompositionContainer itself as a singleton, which is what one usually wants. I have added a generic singleton implementation in my blog that can be used to support this. The import constructor attribute is very simple:
using System; namespace TestComposedIoCContainer { public class ImportConstructorAttribute : Attribute { } }A unit test to display the use of this composition container, followed by the class definitions:
using NUnit.Framework; using System; namespace TestComposedIoCContainer { [TestFixture] public class Program { [Test] public void MainTest() { var container = new CompositionContainer(); container.Bind<ICalculator, Calculator>(); container.Bind<IAdder, Adder>(); container.Bind<IMultiplier, Multiplier>(); var calculator = container.Resolve<ICalculator>(); Console.WriteLine("Calculator resolved!"); int resultAdd = calculator.Add(3, 5); Console.WriteLine("3 + 5 = {0}", resultAdd); int resultMult = calculator.Multiply(4, 8); Console.WriteLine("4 * 8 = {0}", resultMult); } public static void Main(string[] args) { } } } //CLASS DEFINITIONS using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TestComposedIoCContainer { public class Calculator : ICalculator { private IAdder adder; private IMultiplier multiplier; public Calculator() { } [ImportConstructor] public Calculator(IAdder adder, IMultiplier multiplier) { this.adder = adder; this.multiplier = multiplier; } public int Add(int x, int y) { return adder.Add(x, y); } public int Multiply(int x, int y) { return multiplier.Multiply(x, y); } } namespace TestComposedIoCContainer { public interface ICalculator { int Add(int x, int y); int Multiply(int x, int y); } } } namespace TestComposedIoCContainer { public class Adder : IAdder { public int Add(int x, int y) { return x + y; } } } namespace TestComposedIoCContainer { public interface IAdder { int Add(int x, int y); } } namespace TestComposedIoCContainer { public interface IMultiplier { int Multiply(int x, int y); } } namespace TestComposedIoCContainer { public class Multiplier : IMultiplier { public Multiplier() { } public int Multiply(int x, int y) { return x * y; } } } //RESULT OF RUNNING NUNIT UNIT TEST ABOVE: ------ Test started: Assembly: TestComposedIoCContainer.exe ------ Calculator resolved! 3 + 5 = 8 4 * 8 = 32 1 passed, 0 failed, 0 skipped, took 0,46 seconds (NUnit 2.6.2).The code above should only be used for simple IoC scenarios. The IoC container uses very much reflection, which should not be consider the quickest way of resolving objects. The IoC container code can though resolve arbitrarily complex object graphs, when the registering of binds are done correctly, however there is a weakness here - the container does not do loop detection very good. For example, if a "part" or "component" of the composition container imports another parts and this part again resolves that part or some other recursive relation, looping will start to occur. But at the same time - many IoC frameworks are poor at detecting such errors anyways and will also give infinite recursion here. If you have simple needs for IoC resolution, the code above can be used in simple scenarios but most production code should instead choose among the many professional IoC frameworks out there.