Skip to content

5.2.1.311

Compare
Choose a tag to compare
@ENikS ENikS released this 15 Nov 03:23

Important

This release changes how types are registered.

Bug fix

This release is a part of a fix of longest running Unity bug. The problem it addresses is with registrations affecting each other when new registration is the same Type as previously registered mapping.
Following code explains the problem:

IUnityContainer uc = new UnityContainer();

uc.RegisterType<ILogger, MockLogger>(new ContainerControlledLifetimeManager());
ILogger logger = uc.Resolve<ILogger>();

Assert.IsNotNull(logger);
Assert.AreSame(uc.Resolve<ILogger>(), logger); <-- Resolves Singleton as it should

uc.RegisterType<MockLogger>(new TransientLifetimeManager()); <-- Here is the problem

Assert.AreSame(uc.Resolve<ILogger>(), logger); <-- Resolves new MockLogger and fails

The issue is described here

Impact on existing code

To demonstrate impact on existing code please look at this example:

container.RegisterType<ISomething, Something>(new ContainerControlledLifetimeManager());

var a = container.Resolve<Something>();
var b = container.Resolve<ISomething>();

Assert.AreNotSame(a, b);

Due to the this bug resolving a and b returned the same result because ContainerControlledLifetimeManager would be applied to ToType instead of FromType as it should. In other words it would be applied to Something instead of ISomething.

This fix corrects this behavior so only ISomething registers with ContainerControlledLifetimeManager and resolution of Something type will use transient manager.

You could replicate behavior of Unity before this bug fix by simply registering mapped type with proper lifetime manager:

container.RegisterType<Something>(new ContainerControlledLifetimeManager());

For more information see: #35, 163, #164, #165, #170, #177

More strict use of InjectionFactory

This form of registration used to work for registering interface with InjectionFactory but it is no longer allowed.

RegisterType<IService, Service>(new InjectionFactory(c => new Service()));

As written, this code registers both: a mapping between IService and Service and between IService and InjectionFactory. These two are mutually exclusive and create ambiguity which Unity can not resolve.
It should be either one of these but not both:

RegisterType<IService, Service>();
RegisterType<IService>(new InjectionFactory(c => new Service()));

Upgrading to Release v5.2.1

This release fundamentally changes how types are registered with Unity. The rationale behind this change is this issue.

The problem

To explain the problem please look at this example. Prior to this release registering singleton ILogger service like this:

container.RegisterType<ILogger, MockLogger>(new ContainerControlledLifetimeManager(), new InjectionConstructor());

would create two registrations:

  1. A mapping between ILogger to MockLogger
  2. A singleton registration for MockLogger with default constructor.

Calling container.Resolve<ILogger>() resolves singleton instance of MockLogger as expected, and resolving type MockLogger container.Resolve<MockLogger>() would resolve the same instance of MockLogger. Both ContainerControlledLifetimeManager and InjectionConstructor would be associated with MockLogger registration.

Suppose you want to resolve a new MockLogger whenever it is resolved directly like this container.Resolve<MockLogger>(). To do so you would create another registration just for the MockLogger:

container.RegisterType<MockLogger>(new TransientLifetimeManager());

So, now when you call container.Resolve<MockLogger>() it resolves new instance of the MockLogger class and uses constructor with longest list of parameters. All is well and as expected. But now if you try to resolve container.Resolve<ILogger>() it is no longer returns singleton instance of the MockLogger. Now it also returns new MockLogger created with constructor with longest list of parameters.
The subsequent registration overwritten all information associated with ILogger.

The solution

Release 5.2.1 fixes this behavior. Now all information passed to Unity during registration is stored with FromType instead of ToType. So registering type like this:

container.RegisterType<ILogger, MockLogger>(new ContainerControlledLifetimeManager(), new InjectionConstructor());

creates just one registration ILogger and associates LifetimeManager and all provided InjectionMemebers with it. At this point MockLogger is still unregistered.

So, think about it as a RegisteredType and MappedTo type. If you look at initial example:

container.RegisterType<ILogger, MockLogger>(new ContainerControlledLifetimeManager());

ILogger - is a registered type and ContainerControlledLifetimeManager is associated with this type, as well as any InjectionMembers you provide during registration.

Breaking changes

This release breaks a lot of registrations. Anything relaying on TypeTo being registered in mappings will fail. For example:

container.RegisterType<ILogger, MockLogger>(new ContainerControlledLifetimeManager());

Assert.AreSame( container.Resolve<ILogger>(), container.Resolve<MockLogger>()) <-- Will fail now

This could be easily fixed by slightly modifying how types are registered. If you want TypeTo to be available independently you could register it like this:

container.RegisterType<MockLogger>(new ContainerControlledLifetimeManager());
container.RegisterType<ILogger, MockLogger>();

Assert.AreSame( container.Resolve<ILogger>(), container.Resolve<MockLogger>()) <-- Passes

This applies to anything you registering with the type: factories, injection members, interceptors, etc.

Fixing

With some creative searching and sorting these breaking registrations could be identified statically, without running the code. The key is to look for registrations with same TypeTo type. If you see multiple registrations registering same type as implementation type and at least one of them has non transient lifetime it is a good indicator that it might fail after update:

container.RegisterType<ILogger, Logger>(new ContainerControlledLifetimeManager());
...
container.RegisterType<IOtherLogger, Logger>();

To fix just add individual registration for implementation type with proper lifetime manager like so:

container.RegisterType<Logger>(new ContainerControlledLifetimeManager());
...
container.RegisterType<ILogger, Logger>();
...
container.RegisterType<IOtherLogger, Logger>();

Make sure it is registered before other mappings.

Cleanup

Removed IDependencyResolverTrackerPolicy. The interface alone with its policy was invoked during registration and contributed to slowing down performance. Since it is not being used internally within Unity itself it was removed to speed up registration process.