Skip to content

Constructor selection in hierarchies #196

@ENikS

Description

@ENikS

Description

When Unity v5 introduced 'smart' constructor selection it was not thought through very well. It generally works in flat container scenarios, but in hierarchies behavior is somewhat lacking. There are three different scenarios of constructor selection:

Constructor is specified by InjectionConstructor

When constructor is specified by injection, it follows the same logic as the annotated case. The only difference, the registration creates a scope. It means that all dependencies will be resolved from the same container where this registration resides.

Constructor annotated with InjectionConstructor attribute

When one of the constructors of a type marked with InjectionConstructor attribute, the container generates a pipeline with that constructor. If type is registered explicitly, the registration determines the scope of that pipeline.

When resolving, container does not verify dependency availability and simply attempts to resolve all dependencies of the constructor. If the current scope does not have them available, the resolution fails.

Constructor is selected by Unity

This is where things become complicated. There are two algorithms available to select constructor:

Legacy Constructor Selector

Legacy algorithm is available via Unity.Extension.Legacy extension. This algorithm is compatible with 4.0.1 version of Unity and simply selects the longest accessible, non static, non private constructor with supported parameter types.

Smart Constructor Selector

This algorithm will check every accessible constructor and pick one with the longest parameter list the container can successfully resolve.

Unity v5 analyses these constructor and creates resolver based on current state of the container. Subsequent registrations do not affect the constructor, once selected, it remains the same.

Proposed Selection Steps

Injected Constructor

If InjectionConstructor is found, the constructor is selected by the injected member.

Injected constructor always overrides any other selection methods and can only be set by registering type with instance of InjectionConstructor provided to the registration.

If the selected constructor's dependencies could not be satisfied, resolution fails.

Annotated Constructor

This step is applicable to both, registered and unregistered types. If type is registered but no InjectionConstructor is injected, processor will look for InjectionConstructor attribute.

If found, the marked constructor will be used to instantiate the instance.

At runtime no checks are performed. If the selected constructor could not be satisfied by the resolving container, resolution fails.

Legacy Constructor selector

If legacy constructor selector is installed via Legacy extension, it will pick a constructor with the longest list of parameters.

If the selected constructor's dependencies could not be satisfied by the resolving container, resolution fails.

When the extension is installed smart selector never used.

Smart Constructor selector

If neither of above methods produces a valid constructor the container will attempt to find suitable constructor based on current registrations. The selection is performed at runtime and follows these steps:

  • Container creates a list of accessible constructors sorted by number of parameters. The constructors with unsupported parameters, such as Ref or Out are excluded.

  • The container checks each constructor one by one until it finds one it could satisfy with either overridden, injected, or resolved dependencies.

  • Type is instantiated using selected constructor and dependencies.

Metadata

Metadata

Assignees

Labels

Enhancement 🔨Improvement of existing features

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions