第三方 IoC 容器库不少,之所以选择这个是因为只有它支持 .NET Framework 4.0,这样可以让那些古老的程序也能焕发青春。
安装
包地址:https://www.nuget.org/packages/Unity.Container/
注册方式
注册主要分为三种方式
按类型注册
1 2 Container.RegisterType<Foo>(); var foo = Container.Resolve<Foo>();
如果是非接口类型,可以不用注册。
按接口注册
1 2 container.RegisterType<IFoo, Foo>(); var foo = container.Resolve<IFoo>();
如果接口有多个实现,默认按最后一次注册的为准
1 2 3 Container.RegisterType<IFoo, Foo>(); Container.RegisterType<IFoo, Foo2>(); var foo = Container.Resolve<IFoo>();
具名注册,解决同一接口有多个实现的问题
1 2 3 Container.RegisterType<IFoo, Foo>("xxx" ); Container.RegisterType<IFoo, Foo2>(); var foo = Container.Resolve<IFoo>("xxx" );
按实例注册
和按类型注册差不多,只是传入的参数是实例。传入后容器会持有引用,以后检索会返回该实例。
1 2 3 4 5 6 7 8 9 10 var sampleClass = new SampleClass();container.RegisterInstance(sampleClass); var obj1 = (SampleClass)container.Resolve(typeof (SampleClass));obj1.Increase(); Debug.WriteLine(obj1.ReferenceCount); var obj2 = (SampleClass)container.Resolve(typeof (SampleClass));obj2.Increase(); Debug.WriteLine(obj2.ReferenceCount);
这样每次获得的都是同一个实例,起到了单例的效果。
支持具名化,方便检索同类型的不同实例
1 container.RegisterInstance("name" , sampleClass);
工厂函数
注册一个工厂函数,以便在解析时动态创建实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 var container = new UnityContainer();container.RegisterType<School>(); container.RegisterFactory<People>(c => new People()); var x = container.Resolve<School>();x.Foo(); internal class School { private readonly Func<People> _factory; public School (Func<People> factory ) { _factory = factory; } public void Foo () { var people = _factory(); Console.WriteLine(people); } } internal class People { private string Name { get ; set ; } }
生命周期
瞬态生存期
类型:TypeLifetime.Transient
所谓瞬态就是每次解析时都创建一个新的实例。这也是默认的生命周期管理方式。
1 2 3 4 5 6 Container.RegisterType<SampleClass>(); Container.RegisterType<SampleClass>(TypeLifetime.Transient); var obj1 = Container.Resolve<SampleClass>(); var obj2 = Container.Resolve<SampleClass>();
解析生存期
类型:TypeLifetime.PerResolve
这个稍微难理解点,表面上看和Transient是一样的,但是它发生在库内部隐式注入对象时。
直接看例子比较容易理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class A { } class B { public A a { get ; set ; } public B (A arg ) { Debug.WriteLine($"B: {arg.GetHashCode()} " ); } } class C { public C (A arg1, B arg2 ) { Debug.WriteLine($"A: {arg1.GetHashCode()} " ); } }
如果按瞬态类型:
1 2 3 4 5 Container.RegisterType<A>(TypeLifetime.Transient); var x = Container.Resolve<C>();
两次输出结果不同,意味着传递给 C 的对象 a 实例与传递给 B 对象的 a 实例不同。
如果改为PerResolve方式:
1 2 3 4 5 Container.RegisterType<A>(TypeLifetime.PerResolve); var x = Container.Resolve<C>();
顾名思义,就是每次调用Resolve时对于同一类型只会创建一个实例。
容器树单例(全局唯一)
类型:TypeLifetime.Singleton
它确保对象在整个容器树内只被创建一次。
注册类型总是将注册放置在容器树的根,并使其对该容器的所有子容器全局可用。注册是否发生在子容器的根节点并不重要,注册目的地总是根节点。
1 2 3 4 5 6 7 8 IUnityContainer container1 = new UnityContainer(); var container2 = container1.CreateChildContainer();container2.RegisterType<IA, A>(TypeLifetime.Singleton); var obj1 = container1.Resolve<IA>(); var obj2 = container2.Resolve<IA>();Console.WriteLine(obj1 == obj2);
容器树单例(向下传递)
类型:TypeLifetime.PerContainer、TypeLifetime.ContainerControlled
与TypeLifetime.Singleton类似,只不过不会推到根容器去注册,但是会向子容器传递。
1 2 3 4 5 6 7 8 IUnityContainer container1 = new UnityContainer(); var container2 = container1.CreateChildContainer();container1.RegisterType<IA, A>(TypeLifetime.PerContainer); var obj1 = container1.Resolve<IA>();var obj2 = container2.Resolve<IA>();Console.WriteLine(obj1 == obj2);
容器单例
类型:TypeLifetime.Hierarchical、TypeLifetime.Scoped
容器级别的单例。父容器与子容器都可以有自己唯一的实例,互不打扰。
1 2 3 4 5 6 7 8 IUnityContainer container1 = new UnityContainer(); var container2 = container1.CreateChildContainer();container1.RegisterType<IA, A>(TypeLifetime.Scoped); var obj1 = container1.Resolve<IA>();var obj2 = container2.Resolve<IA>();Console.WriteLine(obj1 == obj2);
容器瞬态
类型:TypeLifetime.PerContainerTransient
容器会持有对象,直到容器销毁。且如果对象实现了 IDisposable 接口,则会在释放时调用。
1 2 3 4 var container = new UnityContainer();container.RegisterType<IFoo, Foo>(TypeLifetime.PerContainerTransient); var x = container1.Resolve<IFoo>(); container.Dispose();
线程单例
类型:TypeLifetime.PerThread
每个线程都有自己的一个单例。
1 Container.RegisterType<IFoo, Foo>(TypeLifetime.PerThread);
外部生存期
类型:TypeLifetime.External
容器将持有对象实例的弱引用,每次调用Resolve都会返回该实例。如果实例不再有效,则创建新实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Container.RegisterType<IFoo, Foo>(TypeLifetime.External); var x1 = Container.Resolve<IFoo>(); var x2 = Container.Resolve<IFoo>(); Debug.WriteLine(x1.GetHashCode()); Debug.WriteLine(x2.GetHashCode()); x1 = null ; x2 = null ; GC.Collect(); var x3 = Container.Resolve<IFoo>(); Debug.WriteLine(x3.GetHashCode());
依赖注入
通过一系列以Injection开头的类注入。用的最多的就三类:构造注入、属性注入、方法注入。
构造注入
如果一个类有多个构造函数,容器会选择具有最多参数的构造函数来进行注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MyClass { public MyClass (IA a ) { Debug.WriteLine("1" ); } public MyClass (IA a, IB b ) { Debug.WriteLine("2" ); } public MyClass (IA a, IB b, string name ) { Debug.WriteLine("3" ); } } container.RegisterType<IA, A>(); container.RegisterType<IB, B>(); var myObject = container.Resolve<MyClass>();
使用 InjectionConstructor 标记构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class MyClass { [InjectionConstructor ] public MyClass (IA a ) { Debug.WriteLine("1" ); } public MyClass (IA a, IB b ) { Debug.WriteLine("2" ); } } container.RegisterType<IA, A>(); container.RegisterType<IB, B>(); var myObject = container.Resolve<MyClass>();
根据默认规则,容器会匹配最多参数的构造函数。但可以用[InjectionConstructor]指定构造函数。
通过 InjectionConstructor 配置构造参数信息,以便可以调用指定的构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MyClass { public MyClass (string name ) { Debug.WriteLine(name); } public MyClass (IA a ) { Debug.WriteLine("1" ); } } container.RegisterType<IA, A>(); container.RegisterType<MyClass>(new InjectionConstructor("jack" )); container.Resolve<MyClass>();
容器虽然能匹配到带有IA参数的构造函数,但是仍会调用 InjectionConstructor 指定构造函数。
容器将优先使用注册类型时指定的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class MyClass { public MyClass (string name ) { Debug.WriteLine(name); } [InjectionConstructor ] public MyClass (IA a ) { Debug.WriteLine("1" ); } } container.RegisterType<IA, A>(); container.RegisterType<MyClass>(new InjectionConstructor("jack" )); container.Resolve<MyClass>();
在这个例子中,同时使用了注解和注册时指定两种方式。后者优先级更高。
覆盖参数
1 2 3 4 5 6 7 public class MyClass { public MyClass (string name ) {} } container.RegisterType<MyClass>(new InjectionConstructor("jack" )); container.Resolve<MyClass>(new ParameterOverride("name" , "annie" ));
注意,参数名是区分大小写的 。
覆盖参数主要应用在需要向构造函数传递参数的时候。
属性注入
使用 Dependency 标记属性。
1 2 [Dependency ] public IFoo Foo { get ; set ; }
InjectionProperty
1 2 3 4 5 6 7 8 public class MyClass { public string Name { get ; set ; } } container.RegisterType<MyClass>(new InjectionProperty("Name" , "jack" )); var obj = container.Resolve<MyClass>();Debug.WriteLine(obj.Name);
覆盖属性
1 2 3 4 5 6 7 public class MyClass { public string Name { get ; set ; } } container.RegisterType<MyClass>(new InjectionProperty("Name" )); container.Resolve<MyClass>(new PropertyOverride("Name" , "annie" ));
方法注入
使用 InjectionMethod 标记方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class MyClass { public MyClass (IA a, IB b ) { Debug.WriteLine("2" ); } [InjectionMethod ] public void Foo1 (IA a ) { Debug.WriteLine("InjectionMethod 1" ); } [InjectionMethod ] public void Foo2 (IB b ) { Debug.WriteLine("InjectionMethod 2" ); } } container.RegisterType<IA, A>(); container.RegisterType<IB, B>(); container.Resolve<MyClass>();
方法会在构造函数结束时调用。
InjectionMethod
1 2 3 4 5 6 public class MyClass { public void Foo (IB b ) { } } container.RegisterType<MyClass>(new InjectionMethod("Foo" ));
字段注入
一般很少会用这种方式注入。
1 2 3 4 5 6 7 8 9 public class MyClass { [Dependency ] public IA a; public IB b; } container.RegisterType<MyClass>(new InjectionField("b" ));
注意字段访问权限要求是public。
实践经验
1 2 3 4 5 6 7 8 9 10 11 12 interface IA {}interface IB {}internal sealed class A : IA { internal A (IB b ) {} } internal sealed class B : IB { internal B (IA a ) {} }
这会陷入死循环,从而引发 System.StackOverflowException 异常。
1 2 3 4 5 6 7 8 IUnityContainer container = new UnityContainer(); container.RegisterType<IA, A>(); var childContainer = container.CreateChildContainer();childContainer.RegisterType<IB, B>(); var a = childContainer.Resolve<IA>(); var b = container.Resolve<IB>();
单例是例外,因为单例被推到了父容器中注册
1 2 childContainer.RegisterType<IB, B>(TypeLifetime.Singleton); container.Resolve<IB>();
相关阅读
http://unitycontainer.org/tutorials/Composition/composition.html
https://www.tutorialsteacher.com/ioc/unity-container
The Relationship Zoo