我在以下场景中使用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
不为null,代码将尝试使用以下调用更改生命周期管理器:
container.RegisterType(type, name, lifetimeManager, injectionMembers);
此功能的名称完全具有误导性,因为the documentation明确指出:
使用容器为给定类型和名称注册LifetimeManager。没有为此类型执行类型映射。
不幸的是,不仅名称有误导性,而且文档错误。在调试此代码时,我注意到,如果type
(上述方案中Implementation
)没有映射,则会添加(type
- > {{ 1}})这就是我们在第一个场景中最终进行三次注册的原因。
我已经下载了Unity的来源来解决问题,但我发现了以下单元测试:
type
- 这几乎就是我的情况,并引出了我的问题:
为什么会这样?这是一个概念上的错误,是为了匹配现有的行为而创建的单元测试,但不一定正确,或者我是否遗漏了一些重要内容?
我正在使用Unity v4.0.30319。
答案 0 :(得分:6)
我们需要与原始开发人员联系以确定,但这是我只能假设为什么它被编码为这样......
Unity具有允许解析具体类的功能,即使该类型尚未注册。在这种情况下,Unity必须假设您需要默认的生命周期管理器(瞬态),并且不需要针对该具体类型的注入成员。如果您不喜欢这些默认策略,那么您需要自己注册具体类型并指定自定义。
因此,按照这种思路,当您调用RegisterTypes并指定生命周期管理器和/或注入成员时,Unity会假设您在通过接口和具体类型进行解析时需要该行为。但是如果你没有指定这些策略,那么Unity就不需要具体注册了,因为在解析具体类型时它会回归到默认行为。
我能想出的唯一其他解释是为了插件。 InjectionMember
是可扩展的,因此Unity认为您可以为插件传递自定义行为。因此,它假设您可能希望插件的自定义行为在按惯例注册时应用于接口和具体。
我了解到您在尝试对应用程序引导程序进行单元测试时遇到此问题。我假设您正在测试以确保您注册的类型,并且只注册了那些类型。这会打破你的测试,因为有些测试会发现这个额外的具体注册。
从单元测试的角度来看,我认为你超出了你试图测试的范围。如果你开始使用插件,将会有很多其他注册开始出现(如拦截策略和行为)。由于您的测试方式,这只会增加您现在看到的问题。我建议您更改测试以确保只注册了一个白名单类型,并忽略其他所有类型。具体(或其他额外注册)对您的申请是有利的。
(例如,将您的接口放在白名单中并断言其中的每一个都已注册,并忽略具体已注册的事实)
答案 1 :(得分:1)
我能想到当前行为的一个原因是它重用了现有的Unity功能/实现,并使按惯例注册功能很容易实现。
我正在考虑的现有Unity实现是将类型映射和构建计划分离到不同的策略中,因此如果您正在使用Unity源代码,这将是思考问题的常用方法。另外,从我过去读到的内容来看,Registrations
属性似乎被认为是二等公民,并不打算用于调试,因此进行另一次注册似乎没有是个大问题。或许将这两点放在一起是决定的一部分吗?
除了注册馆藏中的额外项目外,该功能在这种情况下也适用。
然后,如果
lifetimeManager
不为null,代码将尝试使用以下调用更改生命周期管理器
RegisterTypes
方法实际上并未设置LifetimeManager
。如果未指定LifetimeManager
,则未明确注册具体目标类型,Unity在创建对象时依赖于内部默认行为(在本例中为LifetimeManager
的默认TransientLifetimeManager
)。 / p>
但是,如果指定了可能覆盖默认值的任何内容(即LifetimeManager
或InjectionMembers
),则将明确注册具体类型。
应该可以在没有额外注册的情况下使公约注册工作。但是,有一些复杂性需要考虑。如果具体类型实现了许多接口,则可以为具体类型进行多次映射注册,但每次注册都需要它自己的生命周期管理器实例(它们不能被重用)。因此,您可以为每个映射注册创建新的生命周期管理器实例,但(我相信)只会使用最后一个生命周期管理器。那么为了避免不必要的对象创建,也许只能在最后一个类型映射上分配生命周期管这只是我遇到的一种情况 - 需要考虑各种场景和组合。
所以我认为当前的实现是一种简单的方法来实现该功能,而无需专门处理任何边缘情况,唯一的副作用是额外注册。我猜这可能是当时想法的一部分(但我不在那里,所以这只是猜测)。
答案 2 :(得分:1)
如this article中所述,此行为已在5.2.1版中修复:
现在,注册期间传递给Unity的所有信息都将存储在
FromType
,而不是ToType
。因此,像这样注册类型:container.RegisterType<ILogger, MockLogger>(new ContainerControlledLifetimeManager(), new InjectionConstructor());
仅创建一个注册
ILogger
并关联LifetimeManager
所有人都提供了InjectionMemebers
。此时MockLogger
仍未注册。