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.


Keine Kommentare:

Kommentar veröffentlichen