14.7 StyletIoC 杂项
此页面包含其他各种值得一提的点点滴滴,但还不够大,不值得单独放置一个页面。
循环依赖
循环依赖项(下面记录的类型除外)会导致 StackOverflow 异常。提前发现这些问题并非易事,虽然 StackOverflow 异常并不理想,但不值得避免它的复杂性。
父/子循环依赖
假设你有这样的事情:
class Parent
{
public Parent(Child child) {
... }
}
class Child
{
public Parent Parent {
get; set; }
}
您希望 StyletIoC 能够在其中创建 Parent 或 Child 的实例,并适当地创建另一个实例。
诀窍是使用工厂来创建两者,而不是使用容器来解析 Child 的实例并创建 Parent,反之亦然。这有点乱,但是无论如何循环依赖都是乱七八糟的。
Child 的 Parent 属性不能_有_属性[Inject]
,否则该BuildUp
步骤将导致 StackOverflow。
builder.Bind<Parent>().ToFactory(container =>
{
var child = new Child();
container.BuildUp(child); // 如果child需要进行属性注入,则进行构建
var parent = new Parent(child);
child.Parent = parent;
return parent; // parent将由 StyletIoC 自动建立
});
builder.Bind<Child>().ToFactory(container =>
{
var child = new Child();
var parent = new Parent(child);
container.BuildUp(parent); // 如果parent需要进行属性注入,则进行构建
child.Parent = parent;
return child; // child将由 StyletIoC 自动建立
});
14.8 StyletIoC 性能
很难比较不同 IoC 容器的性能。增加的复杂性通常会以性能为代价,因此功能更多的包含几乎总是会变慢。
然而,这些数字看起来非常有趣,所以这里是 Munq 的基准测试,修改后添加了 StyletIoC。
Running 500000 iterations for each use case.
Test: Ticks - mSec - Normalized
No IOC Container: 153,169 - 92.47 - 1.00
StyletIoC: 418,840 - 252.86 - 2.73
Munq: 3,009,272 - 1,816.74 - 19.65
Unity: 48,533,016 - 29,300.02 - 316.86
Autofac: 69,065,954 - 41,696.02 - 450.91
StructureMap: 15,356,998 - 9,271.22 - 100.26
Ninject2: 269,115,738 - 162,468.69 - 1,756.99
Windsor: 191,559,858 - 115,647.19 - 1,250.64
StyletIoCFactory: 3,082,670 - 1,861.05 - 20.13
MunqFactory: 2,777,705 - 1,676.94 - 18.13
AutofacFactory: 18,526,411 - 11,184.64 - 120.95
StructureMapFactory: 15,708,059 - 9,483.16 - 102.55
Ninject2Factory: 197,354,786 - 119,145.67 - 1,288.48
No IOC Container Singleton: 27,382 - 16.53 - 0.18
StyletIoCSingleton: 183,638 - 110.86 - 1.20
MunqSingleton: 411,133 - 248.21 - 2.68
UnitySingleton: 8,048,317 - 4,858.87 - 52.55
AutofacSingleton: 3,205,066 - 1,934.94 - 20.93
StructureMapSingleton: 5,007,675 - 3,023.20 - 32.69
Ninject2Singleton: 30,795,175 - 18,591.45 - 201.05
WindsorSingleton: 2,379,411 - 1,436.48 - 15.53
Hiro: 252,305 - 152.32 - 1.65
在顶部没有后缀的条目中,容器负责确定如何实例化该类型,例如 builder.Bind<ISomeType>().To<SomeType>()
。以 ‘Factory’ 结尾的条目是通过使用 builder.Bind<ISomeType>().To(container => new SomeType(container.Get<ISomeDependency>()))
进行绑定创建的,而以 ‘Singleton’ 结尾的条目是容器配置为始终为每个类型返回相同的实例。
一个有趣的观察是,StyletIoC 并不比简单地实例化类型慢多少——事实上,StyletIoC 实际上实例化类型的速度与本机代码一样快,它正在确定要实例化的类型,这会增加额外的。
另一个是,在 Factory 和 Singleton 情况下,StyletIoC 的速度与 Munq 几乎相同。然而,当容器负责确定如何实例化类型本身的实例时,StyletIoC 的速度要快一个数量级以上。这要感谢对 C# 表达式的一些巧妙使用,这在StyletIoC Technical中有所介绍。
14.9 StyletIoC 技术
此页面是为想要深入研究 StyletIoC 源代码以验证它或提交拉取请求的人准备的背景阅读材料。
表达式:如何快速实例化一个类型
在运行时创建类型实例的传统方法是使用 Activator.CreateInstance
,例如 Activator.CreateInstance(instanceType, new object[] { container.Get(param1Type), container.Get(param2Type) })
。然而,这样做速度非常慢,大多数 IoC 容器不再使用它(如果它们曾经使用过)。
C# 3 引入了表达式树,C# 4 对其进行了扩展。表达式树可以用于生成指定某些操作(例如 a + b
)的表达式,并在运行时将其编译为 IL 作为委托。这允许您使用例如 Expression.New(intanceType)
编写表达式,并将其编译为与手动编写 new Instance()
一样快的委托。
许多 IoC 容器采用了这种方法,并使用它来编写表达式,描述代码中的 Instance(container.Get(param1Type), container.Get(param2Type))
。这比 Activator.CreateInstance
快得多,但仍需要为每个要解析的参数访问 IoC 容器,从而产生额外的开销。
StyletIoC 更进一步,生成描述 new Instance(new Param1Instance(), new Param2Instance())
的代码表达式,这样做速度更快。
当其中一个参数需要属性注入时,也会使用此技巧,创建一个等效于以下内容的表达式:
var param1 = new Param1Instance();
param1.SomeProperty = new SomePropertyInstance();
new Instance(param1, new Param2Instance());
创作者和注册
StyletIoC 主要围绕着两个重要接口展开:ICreator
和 IRegistration
。
ICreator
知道如何提供一个类型的实例 - TypeCreator
知道如何创建一个类型的实例(如果你使用 Bind<..>().To<...>()
注册一个类型,就会使用它),而 FactoryCreator
则知道如何使用你指定的工厂创建一个实例(使用 Bind<..>().ToFactory(...)
)。
IRegistration
负责实例的生命周期,会在需要时使用它拥有的 ICreator
创建类型的新实例。TransientRegistration
每次都会创建一个新实例,而 SingletonRegistration
只会调用它的 ICreator
一次,并缓存结果实例。大多数情况下都使用 TransientRegistration
,但如果你使用 .InSingletonScope()
指定一个单例,则会使用 SingletonRegistration
。
还有一个更复杂的部分,就是 IRegistrationCollection
,它由 SingleRegistration
(拥有单个 IRegistration
)和 RegistrationCollection
(拥有一组 IRegistration
)实现。这主要是为了优化 - IRegistrationCollection
可以被要求提供一个单独的 IRegistration
,也可以提供多个,而 RegistrationCollection
会在前者的情况下引发异常。
在 StyletIoC 的核心是一个字典,它的键是 [serviceType,key]
,值是 IRegistrationCollection
。当你调用 IContainer.Get
时,StyletIoC 会找到正确的 IRegistrationCollection
,并向它请求单个 IRegistration
。然后,它将要求该 IRegistration
提供其类型的实例。
GetAll
注册
当请求注册项的集合时,情况会稍微复杂一些。StyletIoC还有一个字典,其键为 [elementType,key]
,值为 IRegistration
,其中该 IRegistration
是 GetAllRegistration
。给定一个 IEnumerable<T>
,您可以提取 T
,然后使用它从此字典中获取一个 IRegistration
。该 IRegistration
可以创建一个包含所有正确元素的 List<T>
。
该字典由 IContainer.GetAll
和负责构造函数和属性注入的代码的部分使用,当它们遇到 IEnumerable<T>
时。
当需要时,此字典将即时填充。
未绑定的泛型
当您注册一个未绑定泛型类型时(例如 builder.Bind(typeof(IValidator<>)).To(typeof(Validator<>))
),StyletIoC会将一个条目添加到 [未绑定泛型类型, key] => List<UnboundGeneric>
字典中。如果您请求的泛型类型不在注册字典中,StyletIoC会查看是否可以使用此字典中的任何条目构造该类型。如果可以,它将创建一个新的 IRegistration
,并将其添加到注册字典中。
BuilderUppers
另外有一个字典也是解决方案的一部分,它是type => BuilderUpper。每个BuilderUpper都知道如何在该类型的实例上执行属性注入。当您调用IContainer.BuildUp时,会查询此字典,检索相关的BuilderUpper(如果它不存在则创建),并用于构建您的类型。它也被ICreator用于执行属性注入。
项目原地址:https://github.com/canton7/Stylet
上一节:WPF的MVVM框架Stylet开发文档 14.6 StyletIoC 模块
上一篇:WPF的MVVM框架Stylet开发文档 13.验证模型基类 ValidatingModelBase
下一篇:WPF的MVVM框架Stylet开发文档 15. 视图管理器 The ViewManager