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