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.

Expression: How To Quickly Instantiate a Type

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());

Creators and Registrations

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 IRegistrations). 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.

'GetAll' Registrations

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.

Unbound Generics

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.

BuilderUppers

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 ICreators to perform property injection.

⚠️ **GitHub.com Fallback** ⚠️