Article demo code : BarbarianIOC.zip
At work I have used a variety of IOC Containers including
And I have used others such as Unity/StructureMap/AutoFac. They are all very
good and very rich.
For those of you who don't know what IOC stands for, it stands for Inversion
Of Control. Which is described as follows:
In software engineering, inversion of control (IoC) is a programming
technique, expressed here in terms of object-oriented programming, in which
object coupling is bound at run time by an assembler object and is typically not
known at compile time using static analysis.
In traditional programming, the flow of the business logic is determined
by objects that are statically assigned to one another. With inversion of
control, the flow depends on the object graph that is instantiated by the
assembler and is made possible by object interactions being defined through
abstractions. The binding process is achieved through dependency injection,
although some argue that the use of a service locator also provides inversion of
control.
In order for the assembler to bind objects to one another, the objects
must possess compatible abstractions. For example, class A may delegate behavior
to interface I which is implemented by class B; the assembler instantiates A and
B then injects B to A.
In practice, inversion of control is a style of software construction
where reusable code controls the execution of problem-specific code. It carries
the strong connotation that the reusable code and the problem-specific code are
developed independently, which often results in a single integrated application.
Inversion of control as a design guideline serves the following purposes:
- There is a decoupling of the execution of a certain task from
implementation.
- Every module can focus on what it is designed for.
- Modules make no assumptions about what other systems do but rely on
their contracts.
- Replacing modules has no side effect on other modules.
- Inversion of control is sometimes facetiously referred to as the
"Hollywood Principle: Don't call us, we'll call you", because program logic
runs against abstractions such as callbacks.
Wikipedia : up on date 25/02/2013
Thing is I have always wanted to try and make one of these
myself, just to see what is involved. I did not want to go too nuts on this, and
just wanted the following really:
- Instance configuration : singleton / transient
- Simple registration process, maybe some sort of fluent interface
- Use the Expression API to compile into delegates for quick creation of
objects
- Constructor / property injection
- Provide the ability to accept non IOC held constructor parameters
So those couple of points are ALL I wanted to get working. As I say there are
a whole slew of full fledged IOC containers out there (where I have named a few
above), this articles container is more of a learning exercise, that I thought
I would share, in case anyone else is interested in this sort of thing.
I am calling my container BarbarianIOC
as the existing
containers all seems to have these short snappy names, and it's kind of play on
my name, and if you saw me without a shave I do kinda look a bit like a
barbarian.
So there we go. That's essentially what this article is about, but just
before we get into the guts of it, please read this important note below.
IMPORTANT NOTE
I should point out that you should stick to using one of the major IOC containers out there,
as this was an exercise
to see what you needed to do to create your own. That is not to say I am not
happy with it, I totally am, and I think with more tinkering, I could make it
act near enough like one of the "proper" IOC containers out there, but I just know that,
that tinkering will never happen, as I am always
eager to move on to something new. So yeah just stick to using one of the big
"proper" IOC containers out there.
In this section I will should you how to use BarbarianIOC
, and
what that looks like in a typical application
The container can be configured using a fluent like interface something like
this:
Container = new Container();
int someMockAge = 23;
container.RegisterComponents(
new Component().For<Foo>().WithInstanceMode(InstanceMode.Transient),
new Component().For<IBaz>().ImplementedBy<Baz>().WithInstanceMode(InstanceMode.Transient),
new Component().For<SomeIBazDependantClass>().WithInstanceMode(InstanceMode.Singleton),
new Component().For<SomeFooDependantClass>()
.DependsOn(new
{
age=someMockAge
})
.WithInstanceMode(InstanceMode.Transient)
);
container.WireUp();
One of the important issues I wanted to tackle was to not only allow
container generated constructor parameters to be provided, but also user
specified
constructor parameters. I decided to do this using a simple anonymous object,
where you just specify the name of the constructor parameter and its value. An
example of this is shown below, where we can provide these extra non IOC constructor parameters when we
configure the IOC container as follows:
container.RegisterComponents(
new Component().For<Foo>().WithInstanceMode(InstanceMode.Transient),
new Component().For<IBaz>().ImplementedBy<Baz>().WithInstanceMode(InstanceMode.Transient),
new Component().For<SomeFooDependantClass>()
.DependsOn(new
{
age=someMockAge
})
.WithInstanceMode(InstanceMode.Transient)
);
Note the use of the DependsOn(..)
method and the anonymous object
which provides the age
NON IOC created constructor parameter, which
may be needed by some class as shown below:
public class SomeFooDependantClass
{
[DependencyConstructorAttribute]
public SomeFooDependantClass(Foo foo, int age, IBaz baz)
{
}
}
This shows that we have a class that expects an age
constructor parameter
value, along with some other constructor parameters that SHOULD come from the
IOC container. Note the order of the non IOC generated constructor parameter is
in the middle of the constructor arguments, it can be anywhere, full mix and
match of IOC/non IOC created constructor parameters is supported. I was pleased
with that feature.
The IOC container that goes with this article allows for constructor
injection, thus it is able to satisfy the following constructor
public class SomeIBazDependantClass
{
private IBaz somePropBaz;
[DependencyConstructorAttribute]
public SomeIBazDependantClass(IBaz baz)
{
}
}
As previously stated it is also possible to include extra NON IOC constructor params such as the age
parameter we just saw. Here is an example of how you would annotate your class
to tell it which constructor it was going to try and satisfy dependencies for.
public class SomeFooDependantClass
{
[DependencyConstructorAttribute]
public SomeFooDependantClass(Foo foo, int age, IBaz baz)
{
}
}
It can be seen from the code above, that I have chosen to use a specialized
attribute called DependencyConstructorAttribute
which you WILL need
to use, to mark up your chosen constructor with. This will indicate to the IOC
container presented with this article, which constructor should be used to create
an object from the container.
Now there may be some amongst you, who see this as a violation of Separation
Of Concerns (SOC), and to be honest it is
slightly, as our objects now contain IOC specific data in them, by way of these
special DependencyConstructorAttributes
. However without this
DependencyConstructorAttribute
, which ConstructorInfo
to use
would be quite an error prone experience. Should the IOC container go for the
one with the most parameters / the least parameters / the one that has the most
IOC resolvable parameters. It's not an easy decision. I could have ade some
fancy fluent API registration code for it, but in the end I feel justified
in my use of a specialized attribute (DependencyConstructorAttribute
),
which the user can use to adorn their objects, which tells the IOC container,
hey you need to create an object using this constructor.
The order of the IOC / non IOC constructor parameters is NOT important, you can mix and match IOC / non IOC provided constructor parameters as much as you like
. We will get on to how this works later.
The IOC container that goes with this article also allows for property
injection, which can be configured as follows:
public class SomeIBazDependantClass
{
private IBaz somePropBaz;
[Dependency]
public IBaz SomePropBaz
{
get
{
return somePropBaz;
}
set
{
somePropBaz = value;
}
}
}
As with the constructor injection, I opted for using a specialized attribute
DependencyAttribute
, which indicates which properties the IOC
container should try and inject.
When you need a object instance from the container all you need to do is
Resolve
it which is done as follows :
SomeIBazDependantClass x1 = container.Resolve<SomeIBazDependantClass>();
From there you should be able to use the object instance which will have had all its constructor/property IOC/Non
IOC dependencies resolved automatically (providing you got the component
registration configuration correct).
Here is an example of an object that was resolved from the container code
attached to this article, you can clearly see it has all the
properties/constructor vaues provided.
Ok so now I have shown you how to use it, but I bet you want to know how it
works. Well lucky for you, in this section I will talk you through the code and you can get to see the nitty gritty
inner workings. Hopefully that will be interesting.
Registering components (whether they be service instances or concrete types)
is all done using a fluent interface which starts with the Component
builder, which is used to create a ComponentRegistration
instance. The following method(s) are available to aid in the building of a
ComponentRegistration
entry:
Component For<TCOMP>()
: used for registering
a concrete type, and is responsible for creating
a instance
Component ServiceFor<IInt, TCOMP>()
: used for registering a
service which implements a certain interface
Component DependsOn<T>(T dependencies)
: used for supplying
non IOC container satisfied constructor parameter(s)
ComponentRegistration WithInstanceMode(InstanceMode instanceMode)
: which MUST be the final method called in the
Component
builder fluent API
process and it responsible for creating the final
ComponentRegistration
instance.
So with that in mind, lets see how the IOC container uses these
ComponentRegistration
objects. This is all down to 1 simple method called
RegisterComponents
which looks like this, which simply takes the
registrations and adds them to a Container
held collection
public void RegisterComponents(params ComponentRegistration[] registrations)
{
lock (syncLock)
{
foreach (ComponentRegistration componentRegistration in registrations.ToList())
{
components.Add(componentRegistration, null);
}
}
}
So now that we know we are striving to create ComponentRegistration
objects which are passed to the Container
, but what do these
ComponentRegistration
objects look like, well they look like this:
public class ComponentRegistration
{
public Type TypeToLookFor { get; private set; }
public Type TypeToCreate { get; private set; }
public InstanceMode InstanceMode { get; set; }
public bool HasManualConstructorParameters { get; set; }
public List<ConstructorParameterDependency> DependsOnValues { get; set; }
public ComponentRegistration(Type typeToCreate) : this(typeToCreate, typeToCreate)
{
}
public ComponentRegistration(Type typeToLookFor, Type typeToCreate)
{
TypeToLookFor = typeToLookFor;
TypeToCreate = typeToCreate;
DependsOnValues = new List<ConstructorParameterDependency>();
}
}
Let's now
continue to look at the specific Component
builder methods.
This is how you would typically register a concrete type using the Component
builder methods
new Component().For<Foo>().WithInstanceMode(InstanceMode.Transient)
So now let's look at how this works, this is pretty easy and is responsible
for creating a instance of a
ComponentRegistration
that will be returned at the end of the
Component
fluent API, so where you see the "return this
"
within the next couple of methods, that is the fluent API in actio
public Component For<TCOMP>()
{
componentRegistration = new ComponentRegistration(typeof(TCOMP));
return this;
}
This is how you would typically register a interface and implementation type using the Component
builder methods
new Component().ServiceFor<IBaz,Baz>().WithInstanceMode(InstanceMode.Transient)
So now let's look at how this works, this is pretty easy and is responsible
for creating a instance of a
ComponentRegistration
that will be returned at the end of the
Component
fluent API.
public Component ServiceFor<TInt, TCOMP>()
{
componentRegistration = new ComponentRegistration(typeof(TInt), typeof(TCOMP));
return this;
}
The ComponentRegistration WithInstanceMode(InstanceMode instanceMode)
is the simplest of all the Component
builder methods, and simply
sets the InstanceMode
on the ComponentRegistration
that it is
currently building, and then returns the current ComponentRegistration
being built.
Here is ALL the code for the ComponentRegistration
WithInstanceMode(InstanceMode instanceMode) Component
builder method
public ComponentRegistration WithInstanceMode(InstanceMode instanceMode)
{
if (componentRegistration == null)
{
throw new ContainerConfigurationException("Configuration error WithInstanceMode<> MUST be last");
}
else
{
componentRegistration.InstanceMode = instanceMode;
return componentRegistration;
}
}
One of the things that I really wanted to achieve was the ability to satisfy
constructor parameters that were not meant to come from the IOC container. In
essence what I wanted was the ability to mix and match IOC container satisfied
constructor parameter(s) with non IOC container satisfied constructor
parameter(s). Where non IOC container satisfied constructor parameter(s) may be
things like
- App settings
- Connections strings
- Static values
As before we use the Component
fluent interface to build up a
ComponentRegistration
object. The relevant Component
builder method for adding non IOC container satisfied constructor parameter(s)
is the DependsOn( )
method that is shown below.
public Component DependsOn<T>(T dependencies)
{
if (componentRegistration == null)
{
throw new ContainerConfigurationException(
"Configuration error DependsOn<> MUST be called after For<> or ImplementedBy<>");
}
else
{
List<ConstructorParameterDependency> constructorDependencies =
new List<ConstructorParameterDependency>();
foreach (string name in typeof(T).GetConstructors()[0].GetParameters()
.Select(p => p.Name))
{
PropertyInfo property = typeof(T).GetProperty(name);
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression propertyAccess = Expression.Property(param, property);
Expression convert = Expression.Convert(propertyAccess, typeof(object));
Func<T, object> lambda =
Expression.Lambda<Func<T, object>>(convert, param).Compile();
var result = lambda(dependencies);
constructorDependencies.Add(new ConstructorParameterDependency(
property.PropertyType, name, Expression.Constant(result)));
}
if (constructorDependencies.Any())
{
componentRegistration.HasManualConstructorParameters = true;
componentRegistration.DependsOnValues = constructorDependencies;
}
else
{
componentRegistration.HasManualConstructorParameters = false;
}
return this;
}
}
The basic idea here is that we examine an anonymous object that was passed
into the DependsOn( )
method, and we build up a List<ConstructorParameterDependency>
values for each of the properties/property values found on the anonymous object
that was passed into the DependsOn( )
method. These are later
examined by the IOC container to work out which constructor parameter values
should come from where. Essentially the IOC container orders the
ConstructorParameterDependency
based on their Position
property.
We could have used some reflection here instead, as these constructor
parameter values are essentially cached once the 1st instance of a certain type
has been requested from the IOC container, so reflection may have been a better
choice here instead of using Expression
trees, but since everything else is
Expression
based I stuck with it. The performance difference in this case would
be minute, so I stuck to using the Expression
APIs.
We have already seen an example of the usage of this, but just for
completeness here it is again:
container.RegisterComponents(
new Component().For<SomeFooDependantClass>()
.DependsOn(new
{
age = someMockAge
})
.WithInstanceMode(InstanceMode.Transient)
);
At present the IOC container associated with this article only supports 2
instance modes:
- Singleton : Every instance of the object resolved with this instance
mode is expected to be the exact same instance
- Transient : Every instance of the object resolved with this instance
mode is expected to be a new unique instance
The transient mode uses a simple factory which uses a delegate
that the IOC container has cached. Where the
delegate
(Expression.New
is compiled into a delegate
)
is invoked, to return a new object instance. The TransientFactory
can be seen
below:
public class TransientFactory : IFactoryProvider
{
private Delegate objectCreator;
public TransientFactory(Delegate objectCreator)
{
this.objectCreator = objectCreator;
}
public object Create()
{
return objectCreator.DynamicInvoke();
}
}
The singleton mode also uses a simple factory which essentially uses a delegate
that the IOC container has cached. Where the
delegate
(Expression.New
is compiled into a delegate
)
is used as the value of a
Lazy<T>
(which is
really a cheeky way of creating a thread safe singleton), where the new object
instance returned by invoking the delegate used as the Lazy<T>.Value
. The
SingletonFactory
can be seen
below:
public class SingletonFactory : IFactoryProvider
{
private Lazy<object> singletonCreator;
public SingletonFactory(Delegate objectCreator)
{
singletonCreator = new Lazy<object>(() => objectCreator.DynamicInvoke());
}
public object Create()
{
return singletonCreator.Value;
}
}
Based on what we just talked about above, lets assume we have the following
IOC container configuration
Container container = new Container();
int someMockAge = 23;
container.RegisterComponents(
new Component().For<Foo>().WithInstanceMode(InstanceMode.Transient),
new Component().For<IBaz>().ImplementedBy<Baz>().WithInstanceMode(InstanceMode.Transient),
new Component().For<SomeIBazDependantClass>().WithInstanceMode(InstanceMode.Singleton),
new Component().For<SomeFooDependantClass>()
.DependsOn(new
{
age=someMockAge
})
.WithInstanceMode(InstanceMode.Transient)
);
Which when we examine the simple tests as following we get the results we want/expect based on the configuration above (Click below for bigger image)
Once you have registered all your Component
(s) you need to tell
the container to wire everything together. What this does in a nutshell is to
create a Expression.Constant
delegate for each of the constructor parameters making sure to adhere to the
InstanceMode
that was requested, and to also create a final
delegate that is responsible for newing up the registering type, that will be
used by the Resolve<T>
method.
It does this recursively until all
the constructor dependencies of all the register components have matching
delegates to create their constructor parameters, and that there is a final
delegate that is responsible for newing up the registering type again taking
into account the InstanceMode
that was requested.
This is only done once per type. What we then end up with, is a bunch of
constructor parameter value creation delegates (factories essentially) that we
use to create List<ConstructorParameterDependency>
(the same as the
Non IOC generated constructor parameters) for each of the IOC required
constructor parameters. This is then merged with any
ConstructorParameterDependency
instances that were created by using the
DependsOn(..)
method.
The final step is to just sort the List<ConstructorParameterDependency>
using their Position
property, and to use an Expression.New
inside a
Expression.Lambda
to create a factory for creating the instance that is being
examined. This will also take into account the InstanceMode
.
The most relevant methods of the Container
are shown below:
public void WireUp()
{
foreach (ComponentRegistration key in components.Where(c => c.Value == null).Select(c => c.Key).ToList())
{
CreateFactory(key, GetConstructorDelegateForType(key.TypeToCreate), key.InstanceMode);
}
}
private Delegate GetConstructorDelegateForType(Type type)
{
ComponentRegistration componentRegistration = null;
ConstructorInfo ctor = type.GetConstructors()
.Where(x => x.GetCustomAttributes(typeof(DependencyConstructorAttribute), false).Count() > 0).SingleOrDefault();
if (ctor == null)
{
ctor = type.GetConstructors()[0];
}
foreach (var ctorArg in ctor.GetParameters())
{
bool isParamCoveredByManualRegistration = IsParamCoveredByManualRegistration(type, ctorArg);
if (!isParamCoveredByManualRegistration)
{
bool parameterKeyFound = components.Keys.Any(x => x.TypeToCreate == ctorArg.ParameterType ||
ctorArg.ParameterType.IsAssignableFrom(x.TypeToLookFor));
if (!parameterKeyFound)
{
throw new ContainerConfigurationException(string.Format("Couldn't find ctor argument {0}", ctorArg.GetType()));
}
else
{
componentRegistration = FetchComponentRegistration(ctorArg.ParameterType);
if (components[componentRegistration] == null)
{
Delegate delegateForType = GetConstructorDelegateForType(componentRegistration.TypeToCreate);
CreateFactory(componentRegistration, delegateForType, componentRegistration.InstanceMode);
}
}
}
}
List<ConstructorParameterDependency> args = new List<ConstructorParameterDependency>();
foreach (var ctorArg in ctor.GetParameters())
{
bool isParamCoveredByManualRegistration = IsParamCoveredByManualRegistration(type, ctorArg);
if (!isParamCoveredByManualRegistration)
{
componentRegistration = FetchComponentRegistration(ctorArg.ParameterType);
args.Add(new ConstructorParameterDependency(
ctorArg.ParameterType,
ctorArg.Name,
Expression.Constant(components[componentRegistration].Create()),
ctorArg.Position));
}
}
componentRegistration = FetchComponentRegistration(type);
if (componentRegistration != null)
{
if (componentRegistration.DependsOnValues.Any())
{
args.AddRange(componentRegistration.DependsOnValues);
}
}
return Expression.Lambda(Expression.New(ctor, args.OrderBy(x => x.Position)
.Select(x => x.Value).ToArray())).Compile();
}
private bool IsParamCoveredByManualRegistration(Type constructorOwnerType, ParameterInfo constructorArg)
{
ComponentRegistration componentRegistration = FetchComponentRegistration(constructorOwnerType);
if (!componentRegistration.HasManualConstructorParameters)
{
return false;
}
else
{
ConstructorParameterDependency constructorParameterDependency =
componentRegistration.DependsOnValues.SingleOrDefault(
x => x.ArgType == constructorArg.ParameterType && x.Name == constructorArg.Name);
if (constructorParameterDependency != null)
{
constructorParameterDependency.Position = constructorArg.Position;
return true;
}
else
{
return false;
}
}
}
private void CreateFactory(ComponentRegistration key, Delegate @delegate, InstanceMode instanceMode)
{
IFactoryProvider factoryProvider = null;
if (instanceMode == InstanceMode.Transient)
{
factoryProvider = new TransientFactory(@delegate);
}
if (instanceMode == InstanceMode.Singleton)
{
factoryProvider = new SingletonFactory(@delegate);
}
lock (syncLock)
{
components[key] = factoryProvider;
}
}
The resolving of instances is probably the simplest part, as all the hard
work should have already been done by the WireUp()
method, who's job
it is to ensure that there is a IFactoryProvider
created for each
registered Component
. So resolving an object from the container
really just boils down to these 3 simple steps
- Find the
ComponentRegistration
that has the correct
TypeToCreate
as the requested generic type to the Resolve<T>( )
method
- Use the
IFactoryProvider
to create an instance of the
correct type (where the IFactoryProvider
would have already
been created by the WireUp()
method), or throw an
Exception
if we can't find a IFactoryProvider
. If we
get to the point where we chuck an Exception
here, this may be
due to a missing or bad ComponentRegistration
.
- If we get a new object created by the
IFactoryProvider
,
simply go through its properties that are marked up with the
[Dependency]
attribute and satisfy them from the IOC container. Once
this is done simply return the object which should have now had the
following done to it:
- IOC generated constructor parameters should have been satisfied
- Non IOC constructor parameters should have been satisfied
- Properties that are marked as IOC dependencies (ones with
[Dependency]
attribute) should also have been
satisfied
This can be seen within the 2 methods shown below:
public T Resolve<T>()
{
lock (syncLock)
{
IFactoryProvider creator = components.Where(x => x.Key.TypeToCreate == typeof(T)).Select(x => x.Value).SingleOrDefault();
if (creator != null)
{
T newlyCreatedObject = (T)creator.Create();
SatisfyProperties<T>(newlyCreatedObject);
return newlyCreatedObject;
}
else
{
throw new ContainerConfigurationException(string.Format(
"Couldn't create instance of {0} could not find correct IFactoryProvider. This may be down to missing Component registration",
typeof(T).FullName));
}
}
}
private void SatisfyProperties<T>(T newlyCreatedObject)
{
foreach (PropertyInfo prop in newlyCreatedObject.GetType().GetProperties()
.Where(x => x.GetCustomAttributes(typeof(DependencyAttribute), false).Count() > 0))
{
IFactoryProvider factoryProvider = components.Single(x => x.Key.TypeToCreate == prop.PropertyType ||
prop.PropertyType.IsAssignableFrom(x.Key.TypeToLookFor)).Value;
if (factoryProvider != null)
{
prop.SetValue(newlyCreatedObject, factoryProvider.Create(), null);
}
else
{
throw new ContainerConfigurationException(string.Format(
"Couldn't find instance of {0} to use for property injection", prop.PropertyType.FullName));
}
}
}
Anyway that's it. I kind of wrote this one just for fun really. As I say I would
really not recommend anyone use this, as there are some seriously good IOC containers
out there, and I would stick to using one of them. This one is a mere play thing to gain some insight as to how one might go about building a simple IOC container, where
you may not be able to use 3rd party Dlls, or just want something dead simple.
Anyway like I say I just wrote this one for fun really, and if you feel like leaving a
comment / vote that would be most welcome.