2016-01-20 71 views
26

我使用Unity的注册按照惯例机制在以下情形:为什么在指定生命期管理器时注册了两次类型?

public interface IInterface { } 

public class Implementation : IInterface { } 

鉴于Implementation类和它的接口,我以下列方式运行RegisterTypes

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

在此之后电话,unitContainer包含三个注册:

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

当我改变呼叫如下:

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

该容器只包含两个注册:

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

(这是所希望的行为)。

偷看Unity's source code后,我注意到对IUnityContainer.RegisterType应该如何工作有一些误解。

RegisterTypes方法的工作如下(注释表明什么是在上面提出的方案中的值):

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); // ! 
    } 
} 

因为fromTypes不为空,则RegisterTypeMappings添加一种类型的映射:IInterface - >Implementation(正确)。

然后,当lifetimeManager不为空的情况下,代码试图改变一生Manager与以下电话:

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

这个函数的名字完全是误导,因为the documentation明确指出:

RegisterType使用容器给定类型和名称的LifetimeManager。这种类型没有执行类型映射。

不幸的是,不仅名称具有误导性,而且文档也是错误的。调试此代码时,我注意到,当没有从typeImplementation在上面提供的场景中)的映射时,它被添加(作为type - >type),这就是为什么我们最终在第一个场景中有三个注册。

我已经下载了统一的来源,以解决这个问题,但我发现下面的单元测试:

[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); 

    // ... 

- 这几乎恰好我的情况,并导致我的问题:

为什么这是预期的?这是一个概念错误,是为了匹配现有的行为而创建的单元测试,但不一定是正确的,还是我错过了重要的东西?

我正在使用Unity v4.0.30319。

回答

1

,我可以为当前的行为想一个原因是它重用现有的Unity功能/实现,并使得通过约定注册功能相当容易实现。

是我想的

现有的统一实施类型映射的分离和建设计划付诸实施不同的政策,所以如果你是在统一的源代码,这将是考虑的事情常见的方式工作。另外,从我过去读过的内容看,它似乎被认为是二等公民,并不打算只用于调试,因此再次注册似乎不是什么大问题。这两点可能是决定的一部分?

除了注册集合中的额外项目,该功能在这种情况下工作。

然后,在情况下,当lifetimeManager不为空,则代码试图改变寿命管理器具有下列呼叫

RegisterTypes方法实际上并不设置LifetimeManager。如果未指定LifetimeManager,则具体目标类型未明确注册,并且Unity在创建对象时依赖于内部默认行为(在此情况下,默认LifetimeManagerTransientLifetimeManager)。

但是,如果指定了可能会覆盖默认设置的任何内容(即LifetimeManagerInjectionMembers),则会显式注册具体类型。

应该可以通过公约注册登记而无需额外注册。但是,需要考虑一些复杂性。如果一个具体类型实现了许多接口,那么对于具体类型可能会有多个映射注册,但是每个注册都需要它自己的生命周期管理器实例(它们不能被重用)。所以你可以为每个映射注册创建新的生命周期管理器实例,但是(我相信)只有最后一个生命周期管理器会被使用。所以为了避免不必要的对象创建,可能只是在最后一个类型映射上分配生命期管理器这只是我想到的一个场景 - 有各种场景和组合需要考虑。

所以我认为当前的实现是一个简单的方法来实现该功能,而不必专门处理任何边缘情况下唯一的副作用是额外注册。我想这可能是当时思想的一部分(但我不在那里,所以这只是一个猜测)。

+0

当存在多个接口时,被迫拥有默认生命期管理器的多个实例的部分似乎是一个有力的论据,也是目前唯一的具体原因。谢谢! – BartoszKP

7

我们需要深入到原来的开发商是肯定的,但这是什么,我只能假设,为什么它的编码是这个样子......

团结具有一项功能,允许混凝土即使类型尚未注册,类也要解决。在这种情况下,Unity必须假定您需要默认的生命周期管理器(瞬态),并且没有注入成员用于该具体类型。如果您不喜欢这些默认策略,那么您需要自己注册具体类型并指定您的定制。

所以遵循这种思路,当你调用RegisterTypes并且你指定了一个生命期管理器和/或一个注入成员时,那么Unity会在接口和具体类型解析时假设你想要这个行为。但是,如果您不指定这些策略,那么Unity不需要具体的注册,因为在解析具体类型时,它会回落到默认行为。

我能想出的唯一的其他解释是为了插件。 InjectionMember是可扩展的,所以Unity认为你可以传递一个插件的自定义行为。因此,它假设您可能希望将该插件的自定义行为应用于接口和具体按照约定进行注册时。


我知道您在尝试单元测试应用程序引导时遇到了此问题。我假设你正在测试,以确保你注册的类型,只有这些类型被注册。这是打破你的测试,因为一些测试发现这个额外的具体注册。

从单元测试的角度来看,我认为你会超越你试图测试的内容范围。如果你开始使用插件,将会有不少注册开始出现(比如拦截策略和行为)。由于您测试的方式,这只会增加您现在看到的问题。我建议你改变你的测试,以确保只有一个白名单的类型被注册,并忽略其他的一切。具体(或其他附加注册)对您的申请是有益的。

(例如把你的接口在白名单中,并断言这些中的每一个注册的,而忽略了具体注册的事实)

+0

我正在为我的应用程序的引导程序写单元测试,这是非常令人惊讶的。它使我的测试无法读取 - 两个类似的情况产生不同的结果。 TBH,我不明白你的解释。只有一个注册允许:默认生命期管理器和指定的一个。您已经正确地确定了问题的核心:“Unity在通过接口**和具体类型**解析时,假设您想要这种行为 - 但这就是问题:为什么?什么情况证明了这种区别?我不明白为什么它会有所帮助。 – BartoszKP

+0

更新答案与另一个可能的原因(插件),并增加了一个更改您的单元测试的建议。 – TylerOhlsen

+0

我同意你应该测试你在乎的是什么。不测试任何可能的实现。 – Batavia

相关问题