0%

MEF核心笔记(4)细说MEF中的Attribute [下]

今天,我们继续MEF的学习记录,这次内容感觉比较重要,所以,特别放到单独一篇来说,这也是MEF很有特色的地方,相信这其中的亮点,会让你感触良多的。

一、部件的创建规则

我们知道,在目前主流的IoC框架里,注入对象的创建都可以进行个性化配置,例如是否以单例方式创建(也就是共享一个对象,给所有需要注入的地方调用),不仅如此,如果熟悉Remoting技术的朋友应该也会接触到服务对象的实例创建规则,另外WCF服务对象也亦是如此,他们都有各自的创建规则。同样,MEF的部件也有它的创建规则。

涉及到MEF部件的创建规则,首先我们要看下PartCreationPolicyAttribute这个特性,因为部件创建的规则,主要是靠该特性来控制的。该特性只有一个属性CreationPolicy,类型为System.ComponentModel.Composition.CreationPolicy的枚举:

CreationPolicy枚举

对应的ImportAttributeImportManyAttribute,都有一个RequiredCreationPolicy的属性,类型也是此枚举。从这里我们就不难看出,使用PartCreationPolicyAttribute我们可以指定导出部件的创建规则,通过导入的RequiredCreationPolicy的属性我们可以设定导入类型的创建规则。

根据CreationPolicy枚举的值,我们很容易就能看出其代表的意义,Shared代表共享部件,即单例,所有的导入都使用一个实例,如果组合引擎中没有该实例,则会创建,一旦有了,就不会再创建;NonSharedShared相对应,即每次导入都创建一个新的实例,所有导入的实例都拥有自己唯一的状态,数据不共享;Any只是为了匹配导入导出,有下面一张匹配表:

导出的CreationPolicy 导入的CreationPolicy
Any Any、NonShared、Shared
NoneShared NoneShared、Any
Shared Shared、Any

只有满足上面这张表,导入导出才会匹配,下面我们做一个很简单的示例:

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
namespace MEFTest {

class Program {

private static CompositionContainer _container;
static void Main(string[] args) {

var catalog = new AssemblyCatalog(typeof(Program).Assembly);
_container = new CompositionContainer(catalog);

var studentManager1 = _container.GetExportedValue<StudentManager>();
var studentManager2 = _container.GetExportedValue<StudentManager>();

Console.WriteLine(object.ReferenceEquals(studentManager1, studentManager2));
Console.WriteLine(object.ReferenceEquals(studentManager1.Student,
studentManager2.Student));

while (true) {
Console.ReadLine();
}

}
}

//单例导出
[Export, PartCreationPolicy(CreationPolicy.Shared)]
public class Student {
public string Name { get; set; }
public int Age { get; set; }
}

//非单例导出
[Export, PartCreationPolicy(CreationPolicy.NonShared)]
public class StudentManager {
//默认的是 Any
[Import]
public Student Student { get; set; }
}
}

最后输出的是一个falsetrue,看懂了这个示例,你就看懂整个创建规则了,如果没有看懂,抱歉,只能说明,我的示例写得太烂了。

此外,我们可以预先在容器中定义导出,这样的定义不需要Export特性描述类型,并且这样输出的永远是单例:

1
2
_container.ComposeExportedValue<DateTime>(DateTime.Now);
Console.WriteLine(_container.GetExportedValue<DateTime>());

二、元数据和元数据视图

在MEF中,我们可以在导出部件时附加一些数据,而这些附加导出的数据就是元数据,附加导出数据的结构就是元数据视图,这是我觉得MEF中,最令人激动的功能。

导出元数据,我们使用ExportMetadata特性,设置该特性的NameValue,即可导出对应的元数据,我们以示例来说:

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
class Program {

private static CompositionContainer _container;
static void Main(string[] args) {

var catalog = new AssemblyCatalog(typeof(Program).Assembly);
_container = new CompositionContainer(catalog);

var studentManager = _container.GetExportedValue<StudentManager>();
Console.WriteLine(studentManager.Student.Metadata.ClassName);

while (true) {
Console.ReadLine();
}

}
}

public interface IClassMetadata {
string ClassName { get; }

[DefaultValue("")]
string OtherInfo { get; }
}

[Export]
[ExportMetadata("ClassName", "一年级三班")]
public class Student {
public string Name { get; set; }
public int Age { get; set; }
}

[Export]
public class StudentManager {

[Import]
public Lazy<Student, IClassMetadata> Student { get; set; }
}

在该示例中,IClassMetadata即是元数据视图,我们导出的元数据必须满足该接口格式,否则StudentManagerImprot就会失败。我们在Student类上只导出了ClassName,而我们的元数据视图中还有一个OtherInfo的属性,这里需要注意一下,如果要提供默认值,必须标记上DefaultValue,否则如果不赋值(导出)的话,就匹配不了该元数据视图,也就是说,如果我们将DefaultValue去掉,该程序就不会正确执行了(StudentManagerImprot会失败)。

除了使用ExportMetadata特性导出元数据外,我们还可定义自己的导出特性来导出元数据,我们可以继承ExportAttribute,并且一定要有MetadataAttribute特性,以上的示例,我们可以改成这样:

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
public interface IClassMetadata {
string ClassName { get; }
string OtherInfo { get; }
}

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class ExportStudent : ExportAttribute, IClassMetadata {
public ExportStudent()
: base() { }
public ExportStudent(string contractName)
: base(contractName) { }
public ExportStudent(Type contractType)
: base(contractType) { }
public ExportStudent(string contractName, Type contractType)
: base(contractName, contractType) { }

public string ClassName { get; set; }

//如果可选,必须要加上
[DefaultValue("")]
public string OtherInfo { get; set; }

}

[ExportStudent(ClassName = "一年级三班")]
public class Student {
public string Name { get; set; }
public int Age { get; set; }
}

通过继承ExportAttribute,我们可以实现比较强类型的编程,再也不怕字符串拼错,不过相比与ExportMetadata似乎是麻烦了一点点。

三、部件组装通知

这是个比较简单,但又很有用的功能,特别是在我们需要完成一些自动化的操作时(例如日志)。部件组装通知,就是当某个组件引用的部件都能满足导入,在返回已经组装完成的组件之前,先通知该组件。

若要得到通知,我们只要实现IPartImportsSatisfiedNotification接口即可,该接口有一个OnImportsSatisfied的方法,即通知组件部件组装的地方。请看简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Program {

private static CompositionContainer _container;
static void Main(string[] args) {

var catalog = new AssemblyCatalog(typeof(Program).Assembly);
_container = new CompositionContainer(catalog);

var studentManager = _container.GetExportedValue<MyComponent>();

while (true) {
Console.ReadLine();
}

}
}

[Export]
class MyComponent : IPartImportsSatisfiedNotification {
public void OnImportsSatisfied() {
Console.WriteLine("OK!~");
}
}

该示例很简单,但也将IPartImportsSatisfiedNotification淋漓尽致的体现了,由于MyComponent满足了组装条件,所以该通知一定能得到执行。

四、总结

这一篇和前一篇是MEF非常核心的内容,了解到这里,我们基本上已经完全可以胜任MEF的开发使用了。后续打算开发一个程序,来深入体会MEF具体使用,以及设计层面上的思想,大家一起期待吧。