MEF编程指南(前两节)

MEF编程指南(前两节)

在应用程序中使用MEF

在应用程序中使用MEF需要创建一个CompositionContainer的实例,向其中添加可组合的部件,将宿主应用包含进去然后组合。

以下是使用MEF需要用到的步骤:

1、 创建一个宿主类。在接下来的示例中,我们将会使用一个控制台应用,所以宿主也就是Program类了。

2、 引用System.ComponentModel.Composition程序集

3、 添加如下using语句:using System.ComponentModel.Composition;

4、 添加一个Compose()方法,它创建容器的实例并做组合的工作

5、 添加一个Run()方法,它会调用Compose() 方法

6、 在Main()方法中实例化宿主类

注意:在ASP.NET和WPF中无需这一步,因为宿主类是由运行时初始化的。

下面的代码演示了代码的样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;
public class Program {
public static void Main(string[] args) {
Program p = new Program();
p.Run();
}
public void Run() {
Compose();
}
private
void Compose() {
var container = new CompositionContainer();
container.ComposeParts(this);
}
}

7、 定义一个或多个宿主可以导入(import)的导出(exports)。下面的代码中我们将会创建一个叫做IMessageSender的接口。我么还会定义
一个可组合组件–EmailSender类,它通过使用[System.ComponentModel.Composition.Export]特性来导出了一个IMessageSender。

1
2
3
4
5
6
7
8
9
10
public interface IMessageSender {
void Send(string message);
}

[Export(typeof(IMessageSender))]
public class EmailSender: IMessageSender {
public void Send(string message) {
Console.WriteLine(message);
}
}

8、 给宿主类添加属性,每个属性都被[ System.ComponentModel.Composition.Import]修饰。如下就是给Program类添加的一个IMessegeSender类型的导入。

1
2
[Import]
public IMessageSender MessageSender { get; set; }

9、 向容器中添加可组合部件。在MEF中有多种方式可以向容器中添加可组合部件。其中一种就是直接添加可组合部件的实例,还有一种更常用的方式是通过使用目录(catalog),我们稍后将会讲解这一点。

向容器中直接添加组件

在Compose()方法中通过使用ComposeParts()方法来手动添加可组合组件。下面的例子中,一个EmailSender的实例和需要导入它的Program类的实例被添加进了容器中去了。

1
2
3
4
private void Compose() {
var container = new CompositionContainer();
container.ComposeParts(this, new EmailSender());
}

使用AssemblyCatalog来向容器中添加可组合组件

通过使用catalog,容器可以自动创建组件的实例而不需要我们显式的去添加它们。在Compose()方法中创建一个catalog。然后把它传入到容器的构造方
法中去。

下面的例子中,我们通过把当前程序集传入其构造方法中去来创建了一个AssemblyCatalog。我们没有手动添加EmailSender的实例,它将会被自动发
现。

1
2
3
4
5
private void Compose() {
var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}

完成上面各个步骤之后,现在代码应该是如下的样式。

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
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;
public class Program {
[Import]
public IMessageSender MessageSender {
get;
set;
}

public static void Main(string[] args) {
Program p = new Program();
p.Run();
}

public void Run() {
Compose();
MessageSender.Send("Message Sent");
}

private void Compose() {
AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}

public interface IMessageSender {
void Send(string message);
}

[Export(typeof(IMessageSender))]
public class EmailSender: IMessageSender {
public void Send(string message) {
Console.WriteLine(message);
}
}

上面的代码编译并运行时,应用程序和它需要的导入将会被组合起来。Send()方法将会被调用,从而在控制台输出“Message Sent”。

定义可组合部件和契约

可组合部件

可组合部件可以导出其他部件需要的服务,也可以导入其他部件提供的服务。在MEF中可组合部件需要使用
System.ComponentModel.Composition.Import和
System.ComponentModel.Composition.Export来定义其导入和导出。一个可组合部件应该至少包含一个导出。可组合部件可能会
是被显式的添加进容器中去,也可能是通过使用catalog被创建的。MEF发布时带有的默认catalog可以通过Export特性来识别可组合部件。

契约

可组合部件并非是直接依赖于彼此,它们都依赖于一个契约,也就是一个标示字符串。每个导出都会有一个契约,而导入需要声明它需要哪个契约。容器通过使用契约信息来匹配
导入和导出。如果没有指明契约,MEF将会默认使用类型的全限定名作为契约。如果导出中传入了一个类型,MEF也将会使用全限定名。

下面的代码中出现的所有导出契约都是等价的。

1
2
3
4
5
6
7
8
9
10
11
namespace MEFSample {
[Export] public class Exporter {
...
}
[Export(typeof(Exporter))] public class Exporter1 {
...
}
[Export("MEFSample.Exporter")] public class Exporter2 {
...
}
}

接口/抽象契约

通常一个可组合部件导出的都是接口或者抽象类型,而不是具体类型。比如如下的代码中,有两个类都导出了IMessageSender。Notifier类导入一组IM
essageSender,并调用其中每一项的Send()方法。现在新的信息发送器可以很容易的被添加到系统中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Export(typeof(IMessageSender))]
public class EmailSender: IMessageSender {
...
}

[Export(typeof(IMessageSender))]
public class TCPSender: IMessageSender {
...
}

public class Notifier {
[ImportMany]
public IEnumerable < IMessageSender > Senders {
get;
set;
}

public void Notify(string message) {
foreach(IMessageSender sender in Senders) sender.Send(message);
}
}