Mittwoch, 8. November 2017

PantherDI - Part 2: Constructing a container

Basic principle

The basic idea of container construction is that there is a data structure storing the unprocessed registrations which the construction algorithm will take out of the data structure, process and then add the generated providers to the knowledge base.
Processing the unprocessed registrations would basically work as follows:
  1. Get the next entry and remove it from the unprocessed registrations
  2. Resolve its dependencies
  3. For all possible combinations of resolved dependencies create a provider and add it to the knowledge base.

Dependency resolution during container construction

Requirements to the "unprocessed items" data structure

To ensure that the knowledge base contains all registrations needed for dependency resolution of the factory to convert, the algorithm needs to first process all unprocessed registrations for each of the dependencies' contracts first. This means that the data structure to store the unprocessed registrations needs a way of retrieving all registrations that fulfill a contract.

The chosen solution for the data sctructure which stores the unprocessed registrations is to use a dictionary that maps a fulfilled contract to all registrations fulfilling it. This does mean that removal is a bit costly as removing a registration means removing it from all dictionary entries. However the main scenario is that each type only fulfils one contract (which is the expected return type), so this overhead will only come into play in complex configurations.

Resolving the dependencies of the factory

In order to turn a factory into a provider, the construction algorithm needs to resolve its dependencies. For this it has a list of strategies which it will execute and use the concatenated results.
Each of these strategies will be called "Resolver".
While the default resolver is rather trivial, resolvers will also be used to handle certain "automagic" generics like IEnumerable, Lazy and Func. For now the most important part is that a resolver takes the a dependency as well as a function to resolve a dependency (which will run the list of resolvers again) and returns an enumeration of providers for the given dependency.

Accessing the knowlegde base

In its implementation it will store all providers in a dictionary that maps a single contract to an enumeration of the providers that fulfil it. The rest of the constraints will be checked by the resolver.
For encapsulation the knowledge base is a resolver in itself which will only rely on its contents.

Additional Remarks on the chosen implementation


  • The container contains an internal resolution function which is passed to the registered resolvers. This will become interesting in the next blog post when we add implicit handling of certain generics.
  • The container already handles types not resolved by the knowledge base by adding their resolutions to the knowledge base. This is actually work done for the next blog post.


  • While processing the registrations, the container builder will pull a registration from its to-do-list, remove it from that list and then process this single entry until the to-do-list is empty
  • While processing a factory, the builder will encounter dependencies of the factory. Before it tries to resolve those using the registered resolvers, it processes all registrations that fulfill the first contract of the dependency.
    Why only the first contract? Because all registrations that fulfill the other contracts but not the first can't be considered as dependencies, so all dependencies must be in the knowledge base as soon as the first contract has been processed.
  • After resolving all dependencies of a factory, each possible combination of providers for each dependency is converted into a provider, so if there is a partial resolution (=provider with dependencies) for a dependency, there also will be a partial resolution for the currently processed factory.
  • Unlike the container, the ContainerBuilder does not yet handle resolutions done by resolvers other than the knowledge base differently. This is due to the fact that it doesn't support adding custom resolvers yet (unlike the container).


  • The dependency has a custom equality comparer. Still I need to manually invoke it to find an entry in a Dictionary<IDependency, T> or ISet<IDependency>. I would be happy if someone looked into that, so I could use the methods provided by the builtin dictionary and set structures.


  • For now the only way to register a type is by creating a ManualRegistration. However this series of blog posts will also handle the desired ways to create a catalog. These will mainly be via a builder with a fluent interface and via reflection.


The code can be found on GitHub:


This chapter contains a cumulative list of terms that are used within this project.

The central element of PantherDI. 
It offers a way to retrieve instances of objects with all their transitive dependencies resolved.

Information about a single type, enabling a container to create it, resolve its dependencies and use it as dependency.

Any object that serves as the promise that a registration can be used as dependency. A registration can be retrieved only by contract, so the consumer does not need to know its actual type.

Basically the object representation of a function which will create an instance of a registered type.

Dependency (of a Factory or Provider, see below)
A single dependency of a factory is the object representation of one a function parameter. It includes meta-information, for example the contracts the instance passed needs to fulfil.

Created from a factory by resolving all the dependencies that can be resolved using the given registrations.

Knowledge Base (KB)
The knowledge base contains all providers that can be used by the container.

An algorithm that utilizes the knowledge base to return all providers for a given dependency.
It may create new providers from the ones given in the knowledge base, depending on its purpose.

Keine Kommentare:

Kommentar posten