Search code examples
c#.netunity-container

Why is a type registered twice when lifetime manager is specified?


I'm using Unity's Register by convention mechanism in the following scenario:

public interface IInterface { }

public class Implementation : IInterface { }

Given Implementation class and its interface I'm running RegisterTypes in the following way:

unityContainer.RegisterTypes(
    new[] { typeof(Implementation) },
    WithMappings.FromAllInterfaces,
    WithName.Default,
    WithLifetime.ContainerControlled);

After this call, unitContainer contains three registrations:

  • IUnityContainer -> IUnityContainer (ok)
  • IInterface -> Implementation (ok)
  • Implementation -> Implementation (???)

When I change the call as follows:

unityContainer.RegisterTypes(
    new[] { typeof(Implementation) },
    WithMappings.FromAllInterfaces,
    WithName.Default);

The container contains only two registrations:

  • IUnityContainer -> IUnityContainer (ok)
  • IInterface -> Implementation (ok)

(this is the desired behaviour).

After peeking into Unity's source code, I've noticed that there is some misunderstanding about how IUnityContainer.RegisterType should work.

The RegisterTypes method works as follows (the comments indicate what are the values in the scenarios presented above):

foreach (var type in types)
{
    var fromTypes = getFromTypes(type); // { IInterface }
    var name = getName(type);           // null
    var lifetimeManager = getLifetimeManager(type); // null or ContainerControlled
    var injectionMembers = getInjectionMembers(type).ToArray(); // null

    RegisterTypeMappings(container, overwriteExistingMappings, type, name, fromTypes, mappings);

    if (lifetimeManager != null || injectionMembers.Length > 0)
    {
        container.RegisterType(type, name, lifetimeManager, injectionMembers);   // !
    }
}

Because fromTypes is not empty, the RegisterTypeMappings adds one type mapping: IInterface -> Implementation (correct).

Then, in case when lifetimeManager is not null, the code attempts to change the lifetime manager with the following call:

container.RegisterType(type, name, lifetimeManager, injectionMembers);

This function's name is completely misleading, because the documentation clearly states that:

RegisterType a LifetimeManager for the given type and name with the container. No type mapping is performed for this type.

Unfortunately, not only the name is misleading but the documentation is wrong. When debugging this code, I've noticed, that when there is no mapping from type (Implementation in the scenarios presented above), it is added (as type -> type) and that's why we end up with three registrations in the first scenario.

I've downloaded Unity's sources to fix the problem, but I've found the following unit test:

[TestMethod]
public void RegistersMappingAndImplementationTypeWithLifetimeAndMixedInjectionMembers()
{
    var container = new UnityContainer();
    container.RegisterTypes(new[] { typeof(MockLogger) }, getName: t => "name", getFromTypes: t => t.GetTypeInfo().ImplementedInterfaces, getLifetimeManager: t => new ContainerControlledLifetimeManager());

    var registrations = container.Registrations.Where(r => r.MappedToType == typeof(MockLogger)).ToArray();

    Assert.AreEqual(2, registrations.Length);

    // ...

- which is almost exactly my case, and leads to my question:

Why is this expected? Is it a conceptual mistake, a unit test created to match existing behaviour but not necessarily correct, or am I missing something important?

I'm using Unity v4.0.30319.


Solution

  • This behavior was fixed in version 5.2.1, as explained in this article:

    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.