StyletIoC Technical - canton7/Stylet GitHub Wiki
This page is background reading for someone wanting to dig into the StyletIoC source code, either to verify it or to submit a pull request.
The traditional way of creating an instance of a type at runtime is using Activator.CreateIntance
, e.g. Activator.CreateInstance(instanceType, new object[] { container.Get(param1Type), container.Get(param2Type) })
. However, this is deadly slow, and most IoC containers no longer use it (if they ever did).
C# 3 introduced Expression Trees, which were expended on by C# 4. Expression trees are many things, but one of their abilities is to allow you to generate an expression specifying some operation (e.g. a + b
), and compile that down to IL at run-time, as a delegate. This allows you to write an expression using e.g. Expression.New(intanceType)
, and compile that down to a delegate which is as fast as doing new Instance()
yourself.
Many IoC containers have adopted this approach, and use it to write expressions which describe the code new Instance(container.Get(param1Type), container.Get(param2Type))
. This provides a significant speed-up over Activator.CreateInstance
, but still requires hitting the IoC container for each parameter to be resolved, which incurs additional overhead.
StyletIoC takes this one step further, and generates an expression which describes the code new Instance(new Param1Instance(), new Param2Instance())
, which is significantly faster.
This trick is also used when one of the arguments requires property injection, where an Expression equivalent to the following is created:
var param1 = new Param1Instance();
param1.SomeProperty = new SomePropertyInstance();
new Instance(param1, new Param2Instance());
StyletIoC is mainly centered around two important interfaces: ICreator
and IRegistration
.
An ICreator
knows how to provide an instance of a type - the TypeCreator
knows how to create an instance of a type (and is used if you register a type using Bind<..>().To<...>()
), while the FactoryCreator
knows how to create an instance using a factory you've specified (using Bind<..>().ToFactory(...)
).
An IRegistration
is responsible for the lifetime of an instance, and will create a new instance of the type where necessary using the ICreator
which it owns. The TransientRegistration
will create a new instance each time one's requested, while the SingletonRegistration
will call its ICreator
only once, and will cache the resulting instance. The TransientRegistration
is used most of the time, but if you specify a singleton using .InSingletonScope()
, a SingletonRegistration
will be used.
There's a further piece to the puzzle, which is IRegistrationCollection
, implemented by SingleRegistration
(which owns a single IRegistration
) and RegistrationCollection
(which owns a collection of IRegistration
s). This is mainly an optimisation - an IRegistrationCollection
can be asked to supply either a single IRegistration
, or multiple ones, and a RegistrationCollection
will throw an exception in the former case.
At the heart of StyletIoC is a dictionary of [serviceType, key] -> IRegistrationCollection
. When you call IContainer.Get
, StyletIoC will find the correct IRegistrationCollection
for the type you specified, and ask it for a single IRegistration
. It will then ask that IRegistration
to provide an instance of its type.
Things get slightly more complex when collections of registrations are requested. StyletIoC has a further dictionary of [elementType, key] => IRegistration
, where that IRegistration
is a GetAllRegistration
. Given an IEnumerable<T>
, you can extract the T
, then use it to get an IRegistration
from this dictionary. That IRegistration
can create a List<T>
, populated with all the correct elements.
This dictionary is consulted by IContainer.GetAll
, and parts of the code responsible for constructor and property injection, when they encounter an IEnumerable<T>
.
This dictionary is populated on-the-fly when when required.
When you register an unbound generic type (e.g. builder.Bind(typeof(IValidator<>)).To(typeof(Validator<>))
), StyletIoC adds an entry to a dictionary of [unboundGenericType, key] => List<UnboundGeneric>
. If you request a generic type which isn't in the registrations dictionary, StyletIoC will see whether it can construct that type using any of the entries in this dictionary. If it can, it will create a new IRegistration
, and add it to the registrations dictionary.
There's a final dictionary which completes the puzzle, one of type => BuilderUpper
. Each BuilderUpper knows how to perform property injection on an instance of that type. When you call IContainer.BuildUp
this is consulted, and the relevant BuilderUpper
retrieved (or created if it didn't already exist) and used to build up your type. It's also used by the ICreator
s to perform property injection.