为什么在指定生命周期管理器时注册了两次类型?

Bar*_*zKP 26 .net c# unity-container

我在以下场景中使用Unity的会议注册机制:

public interface IInterface { }

public class Implementation : IInterface { }
Run Code Online (Sandbox Code Playgroud)

给定Implementation类及其接口我正在RegisterTypes以下列方式运行:

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

此次通话后,unitContainer包含三个注册:

  • IUnityContainer- > IUnityContainer (好的)
  • IInterface- > Implementation(好的)
  • Implementation- > Implementation(???)

当我按如下方式更改呼叫时:

unityContainer.RegisterTypes(
    new[] { typeof(Implementation) },
    WithMappings.FromAllInterfaces,
    WithName.Default);
Run Code Online (Sandbox Code Playgroud)

容器只包含两个注册:

  • IUnityContainer- > IUnityContainer(好的)
  • IInterface- > Implementation(好的)

(这是期望的行为).

在窥探Unity的源代码之后,我注意到对于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);   // !
    }
}
Run Code Online (Sandbox Code Playgroud)

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

然后,如果lifetimeManager不为null,代码将尝试使用以下调用更改生命周期管理器:

container.RegisterType(type, name, lifetimeManager, injectionMembers);
Run Code Online (Sandbox Code Playgroud)

此函数的名称完全具有误导性,因为文档明确指出:

使用容器为给定类型和名称注册LifetimeManager.没有为此类型执行类型映射.

不幸的是,这个名称不仅具有误导性,而且文档也是错误的.在调试这段代码时,我注意到,当没有映射时type(Implementation在上面提供的场景中),它被添加(as type- > type),这就是我们在第一个场景中最终进行三次注册的原因.

我已经下载了Unity的来源来解决问题,但我发现了以下单元测试:

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

    // ...
Run Code Online (Sandbox Code Playgroud)

- 几乎就是我的情况,并引出了我的问题:

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

我正在使用Unity v4.0.30319.

Tyl*_*sen 6

我们需要与原始开发人员联系以确定,但这是我只能假设为什么它被编码为这样...

Unity具有允许解析具体类的功能,即使该类型尚未注册.在这种情况下,Unity必须假设您需要默认的生命周期管理器(瞬态),并且不需要针对该具体类型的注入成员.如果您不喜欢这些默认策略,则需要自己注册具体类型并指定自定义.

因此,按照这种思路,当您调用RegisterTypes并指定生命周期管理器和/或注入成员时,Unity会假设您在通过接口和具体类型进行解析时需要该行为.但是,如果您不指定这些策略,那么Unity不需要具体注册,因为在解析具体类型时它将回退到默认行为.

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


我知道您在尝试对应用程序引导程序进行单元测试时遇到了这个问题.我假设您正在测试以确保您注册的类型,并且只注册了那些类型.这会打破你的测试,因为有些测试会发现这个额外的具体注册.

从单元测试的角度来看,我认为你超出了你试图测试的范围.如果你开始使用插件,将会有很多其他注册开始出现(如拦截策略和行为).由于您的测试方式,这只会增加您现在看到的问题.我建议您更改测试以确保只注册了一个白名单类型,并忽略其他所有类型.具体(或其他额外注册)对您的应用程序是良性的.

(例如,将您的接口放在白名单中并断言其中的每一个都已注册,并忽略具体已注册的事实)


Bar*_*zKP 2

此行为已在版本 5.2.1 中修复,如本文所述:

现在,注册期间传递给 Unity 的所有信息都存储为 , FromType而不是ToType。所以注册类型如下:

container.RegisterType<ILogger, MockLogger>(new ContainerControlledLifetimeManager(), new InjectionConstructor());
Run Code Online (Sandbox Code Playgroud)

仅创建一个注册ILogger和关联LifetimeManager ,并提供所有InjectionMemebers这些。此时MockLogger 仍处于未注册状态。