Unity是一个.NET Framework用于依赖注入的容器。依赖注入一般分为三个阶段:Register, Resolve和Dispose。Unity支持三种方式的Register:
- Instance registration
- Type registration
- Factory registration
那这三种方式分别用于什么场景呢?简单来说,Instance registration和Type registration较为常见,直接通过ResolvedParameter解析依赖的对象,Factory registration更灵活,可以通过调用方法创建对象。举个例子,如果注入对象所依赖的参数是一个List<T>或Dictionary<T1, T2>,而T又是自定义的类,那么就必须使用Factory registration。本文主要介绍的就是如何解析一个自定义类的List或Dictionary。
RegisterType示例
从例子切入,定义IAnimal Interface,并有Tiger和Deer两个实现,还有一个Zoo类,聚合了Tiger和Deer:
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
28
29
30
31
32
33
34
35
36
37
38
| public interface IAnimal
{
void Run();
}
public class Tiger : IAnimal
{
public void Run()
{
Console.WriteLine("Tiger runs away");
}
}
public class Deer : IAnimal
{
public void Run()
{
Console.WriteLine("Deer runs away");
}
}
public class Zoo
{
private readonly IAnimal _tiger;
private readonly IAnimal _deer;
public Zoo(IAnimal tiger, IAnimal deer)
{
_tiger = tiger;
_deer = deer;
}
public void Play()
{
_tiger.Run();
_deer.Run();
}
}
|
注入并调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| class Program
{
static void Main(string[] args)
{
// Registration
var container = new UnityContainer();
container.RegisterType<IAnimal, Tiger>("Tiger", new InjectionConstructor());
container.RegisterType<IAnimal, Deer>("Deer", new InjectionConstructor());
container.RegisterType<Zoo, Zoo>("Zoo", new InjectionConstructor(
new ResolvedParameter<IAnimal>("Tiger"),
new ResolvedParameter<IAnimal>("Deer")));
// Resolve the instances
var zoo = container.Resolve<Zoo>("Zoo");
zoo.Play();
Console.ReadKey();
}
}
|
运行结果:
1
2
| Tiger runs away
Deer runs away
|
RegisterFactory示例
RegisterFactory (已淘汰的InjectionFactory)可以用Unity创建的Factory创建对象,这是它与RegisterInstance和RegisterType的主要区别。
继续上面的例子,为了更灵活,考虑让Zoo类接受animal list作为参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class Zoo
{
private readonly List<IAnimal> _animals;
public Zoo(List<IAnimal> animals)
{
_animals = animals;
}
public void Play()
{
foreach (var animal in _animals)
{
animal.Run();
}
}
}
|
那么如何注入List?比较直观的想法是这样(错误代码):
1
2
3
4
5
6
7
8
9
| container.RegisterType<List<IAnimal>>(
"AnimalList",
new InjectionConstructor(
new List<IAnimal>
{
new ResolvedParameter<IAnimal>("Tiger"),
new ResolvedParameter<IAnimal>("Deer")
})
);
|
遇到编译错误:
1
| Argument 1: cannot convert from 'Unity.Injection.ResolvedParameter<ConsoleApp1.IAnimal>' to 'ConsoleApp1.IAnimal'
|
报错原因在于List中是IAnimal类型,而ResolvedParameter是Unity.Injection.ResolvedParameter类型,二者不匹配,编译错误。在List内部ResolveParameter是运行时在List的构造函数中创建对象,而InjectionConstructor()中只能静态Resolve对象。所以RegisterType并不能达到在List中Resolve对象的目的。
RegisterFactory可以用Factory通过执行一段代码解决问题:
1
2
3
4
5
6
7
8
| container.RegisterFactory<List<IAnimal>>(
"AnimalList",
m => new List<IAnimal>
{
m.Resolve<IAnimal>("Tiger"),
m.Resolve<IAnimal>("Deer"),
}
);
|
RegisterFactory接受Func<IUnityContainer, object>为一个Factory,在List初始化时Resolve两个IAnimal对象。
直接解析自定义类Collection
不用RegisterFactory也可直接解析自定义类Collection,但有两个条件:
- 待解析的Collection必须是array []或
IEnumerable<T> - 所有被注册为该自定义类的类型都会被加入Collection中,不能解析出所有自定义类的一个子集。听起来很绕,可以看下面实例。
先修改Zoo的参数,由List<IAnimal>变为IEnumerable<IAnimal>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class Zoo
{
private readonly IEnumerable<IAnimal> _animals;
public Zoo(IEnumerable<IAnimal> animals)
{
_animals = animals;
}
public void Play()
{
foreach (var animal in _animals)
{
animal.Run();
}
}
}
|
然后可通过下面语句注册Zoo:
1
2
3
4
| container.RegisterType<Zoo, Zoo>("Zoo",
new InjectionConstructor(
new ResolvedParameter<List<IAnimal>>()
));
|
但这种注册方式会把所有已注册的IAnimal都加入List<IAnimal>,如果只想把Tiger加入其中则不行,必须使用RegisterFactory的方式。
RegisterFactory vs RegisterType
考虑Zoo(IAnimal tiger, IAnimal deer)的两种不同注入方式:
1
2
3
| container.RegisterType<Zoo, Zoo>("Zoo", new InjectionConstructor(
new ResolvedParameter<IAnimal>("Tiger"),
new ResolvedParameter<IAnimal>("Deer")));
|
1
2
3
4
5
| container.RegisterFactory<Zoo>("Zoo",
m => new Zoo(
m.Resolve<IAnimal>("Tiger"),
m.Resolve<IAnimal>("Deer"))
);
|
在container.Resolve(“Zoo”)时,RegisterType直接通过container创建对象,而RegisterFactory是通过Zoo Factory调用委托new Zoo()的方式创建对象(在“m => new Zoo(”处可设置断点查看)。
简而言之,RegisterFactory是一种比RegisterType更为灵活的注入方式,可以通过注入一个Factory的委托执行代码进行对象创建,但RegisterFactory用起来更为复杂。
完整代码
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
| using System;
using System.Collections.Generic;
using Unity;
using Unity.Injection;
namespace ConsoleApp1
{
public interface IAnimal
{
void Run();
}
public class Tiger : IAnimal
{
public void Run()
{
Console.WriteLine("Tiger runs away");
}
}
public class Deer : IAnimal
{
public void Run()
{
Console.WriteLine("Deer runs away");
}
}
public class Zoo
{
private readonly IEnumerable<IAnimal> _animals;
public Zoo(IEnumerable<IAnimal> animals)
{
_animals = animals;
}
public void Play()
{
foreach (var animal in _animals)
{
animal.Run();
}
}
}
public class SimpleZoo
{
private readonly IEnumerable<IAnimal> _animals;
public SimpleZoo(IEnumerable<IAnimal> animals)
{
_animals = animals;
}
public void Play()
{
foreach (var animal in _animals)
{
animal.Run();
}
}
}
class Program
{
static void Main(string[] args)
{
// Registration
var container = new UnityContainer();
container.RegisterType<IAnimal, Deer>("Deer", new InjectionConstructor());
container.RegisterType<IAnimal, Tiger>("Tiger", new InjectionConstructor());
container.RegisterFactory<List<IAnimal>>(
"AnimalList",
m => new List<IAnimal>
{
m.Resolve<IAnimal>("Tiger")
}
);
container.RegisterType<Zoo, Zoo>("Zoo",
new InjectionConstructor(
new ResolvedParameter<List<IAnimal>>()
));
container.RegisterType<SimpleZoo, SimpleZoo>("SimpleZoo",
new InjectionConstructor(
new ResolvedParameter<List<IAnimal>>("AnimalList")
));
// Resolve the instances
Console.WriteLine("Zoo: ");
var zoo = container.Resolve<Zoo>("Zoo");
zoo.Play();
Console.WriteLine();
Console.WriteLine("SimpleZoo: ");
var simpleZoo = container.Resolve<SimpleZoo>("SimpleZoo");
simpleZoo.Play();
Console.ReadKey();
}
}
}
|
Reference
C# Unity register a list of registered types and resolve by adding N instances
Registering Types and Object Instances
Dependency Injection with Unity
Resolving collections of Objects of a Particular Type