Mittwoch, 28. Februar 2018

PantherDI - Part 8: Progress-Update

This blogpost does not come with a released version. Instead I just want to give an update about the progress I made and where I still want to go before the release.

PRISM integration


The first and big change was the PRISM integration library which I decided to create as a separate repository that uses PantherDI as NuGet-Package.
While implementing this, I noticed that I needed to register some types only when the user of PantherDI hadn't already registered implementations for them.
The container (or container builder) does not have any support for this and I didn't want to give the PRISM integration library any control over PantherDI that a normal developer wouldn't have.
Thus I started my first approach of registering a type as a fallback (meaning it'll only be used if no type is already registered).
The idea was to create a container which only contains the fallback types and add this to the original container in a way that it's only used if the original container can't find anything.
Thus I started working on the general topic of adding containers to new containers and came up with three ways this could be done:

  • Adding a container as a resolver
  • Adding the providers of a container as factories
  • Creating child containers (which can access their parents content but not otherwise)

Nested Containers

Adding a container as resolver

This part was rather easy at first:
To add a container as resolver, I made it implement IResolver and implemented the Resolve method to simply call on the knowledge base and its resolvers to do the job.
I then refactored this, so a container needs to explicitely converted to a resolver using the AsResolver method.

Adding the providers of a container as factories

To do this I needed to create some code that creates a catalog from a container by iterating through all its resolvers and converting their content to registrations.
Since there are two resolver types that directly contain registrations and two types that contain more resolvers, I made this a depth-first-search through all resolvers.
For now only the knowledge base and the unconverted registrations in a RegistrationConverter can be converted. All resolvers implementing IEnumerable<IResolver> will be recursively processed.

Creating child containers

To create a child container in runtime I plan to make the interface IContainerBuilder resolvable.
When resolved, the container builder will build children of the container that resolved it.
That means that the new container has full access to the content of its parent, but the parent container is unable to resolve the content of its child.

This is added because normally the content of a container is immutable once it's been created.
If a container is disposed however, it will also dispose all of its children.

All singletons created will be part of the container that created them.
So child containers can not (yet) be used to indicate lifetime scopes.
Lifetime scoping is a feature planned for the future however.

Fallback registrations

However this does not solve the original problem of registering types as fallbacks for when no other registration exists.
I'm pondering two options right now - and will most likely add both:


  • Adding optional priorities to catalogs, registrations and factories
    Only the providers with the highest priority will be used, others will be ignored. A priority on a node deeper in the registration tree overrides the value it inherits from its parents. No priority means highest priority.
  • Making it possible to specify fallback catalogs.
    This would be implemented by making the ContainerBuilder store a second List<Catalogs>.
    The factories from the second list will only be considered for a contract if there isn't yet factory present for it.


Then the PRISM integration library could be completed and a simple sample project created. The sample will grow and show how-tos for using PantherDI with PRISM.

Donnerstag, 4. Januar 2018

PantherDI - Part 7: Generated Code

In this blogpost I will describe implementation of features that were left because they needed code generation. Between christmas and new years I sometimes felt the urge to continue working on PantherDI and thus read up on code generation using T4-Text-Templates and implemented these features.

Resolving any Func<..., T>

Goal of resolution of Func-Types is that all of them can be resolved, no matter how many parameters the function has. Note that when I use "parameters" in this post, I don't talk about the type parameters of Func<...>, but the parameters of the function itself or in other words the type parameters except for the last one (which is the result type of the function). Since there is no meta-type which matches all of them, there needs to be code for all possible number of parameters. This code is mainly copy and paste plus adding a new entry into some lists for each added parameter. Thus I decided to solve that problem using code generation and learning about T4-Text-Templates.
First of all I refactored the Func1Resolver, so the main logic resides in a new static method of a new class. This method takes the parameters of the function as an array and uses them to create the actual provider. The algorithm for creating the provider is the same as it was for the Func1Provider:
  • Get all providers for the return type that fulfil the given contracts. Take into account that one contract might need to be replaced with the return type
  • Filter out providers that don't depend on the parameters of the function
  • Wrap each provider to returns a function that when called
    • adds the function parameters to the dictionary of resolved types that were passed to the wrapped provider
    • call the wrapped provider with the new dictionary
    • return its result
Next I created the text template which calls the new base function. While doing so I moved getting all providers for the return type into each generated Func#Provider, because replacing the Func<...>-Type in the list of expected contracts with the return type of the function can only be done when the Func<...>-Type is known.

Registering any Func<..., T> as factory

Registering custom factories was not implemented at all before because the only way to create a factory was using a MethodInfo or ConstructorInfo. But enabling to register any function as factory is a major part in any dependency framework in my opinion, so this was a rather important step to me. Thus this change is actually two changes in one:
  • Enabling the user to create a factory from any Func<..., T>
  • Enabling the user to register any IFactory

Create a factory from any Func<..., T>

This calls for a new factory type, the DelegateFactory. But since I don't want to create the whole DelegateFactory using text-templates, I split it up into two partial classes. One contains the actual functionality of the DelegateFactory (which means the implementation of IFactory) and a base constructor taking a Func<object[], object> (the implementation of the Execute-function). The other contains generated static creation methods that each take a different Func<..., T>, convert it to an Func<object[], object> by simply mapping the entries of the array to the formal parameters of the input function.

Registering any factory to a ContainerBuilder

To register any factory to a ContainerBuilder two new methods have been added:
WithFactory(IFactory, params object[]) expects the actual return type of the factory for creating a registration for that type and the factory itself. Optionally a list of additional contracts that only the result of this factory fulfils can be added. It just registers the factory with default settings.
RegisterFactory(IFactory) expects the same parameters but returns a type on which the settings for the registration can be changed. This way the factory can be registered as a factory for a singleton for example.
In addition to that a class with extension methods for the ContainerBuilder is generated that allows to directly register a Func<..., T> as factory to the ContainerBuilder.


Code

The code after implementing this blogpost can be found on GitHub under the Tag v1.3.0: Link

Glossary

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

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

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

Contract
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.

Factory
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.

Provider
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.

Resolver
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.

Processing
Often means converting a Factory into a Provider by resolving all its dependencies that can be resolved.