今天,我们继续MEF的学习记录,这次内容感觉比较重要,所以,特别放到单独一篇来说,这也是MEF很有特色的地方,相信这其中的亮点,会让你感触良多的。
一、部件的创建规则
我们知道,在目前主流的IoC框架里,注入对象的创建都可以进行个性化配置,例如是否以单例方式创建(也就是共享一个对象,给所有需要注入的地方调用),不仅如此,如果熟悉Remoting
技术的朋友应该也会接触到服务对象的实例创建规则,另外WCF服务对象也亦是如此,他们都有各自的创建规则。同样,MEF的部件也有它的创建规则。
涉及到MEF部件的创建规则,首先我们要看下PartCreationPolicyAttribute
这个特性,因为部件创建的规则,主要是靠该特性来控制的。该特性只有一个属性CreationPolicy
,类型为System.ComponentModel.Composition.CreationPolicy
的枚举:
对应的ImportAttribute
、ImportManyAttribute
,都有一个RequiredCreationPolicy
的属性,类型也是此枚举。从这里我们就不难看出,使用PartCreationPolicyAttribute
我们可以指定导出部件的创建规则,通过导入的RequiredCreationPolicy
的属性我们可以设定导入类型的创建规则。
根据CreationPolicy
枚举的值,我们很容易就能看出其代表的意义,Shared
代表共享部件,即单例,所有的导入都使用一个实例,如果组合引擎中没有该实例,则会创建,一旦有了,就不会再创建;NonShared
和Shared
相对应,即每次导入都创建一个新的实例,所有导入的实例都拥有自己唯一的状态,数据不共享;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 { [Import] public Student Student { get; set; } } }
|
最后输出的是一个false
和true
,看懂了这个示例,你就看懂整个创建规则了,如果没有看懂,抱歉,只能说明,我的示例写得太烂了。
此外,我们可以预先在容器中定义导出,这样的定义不需要Export
特性描述类型,并且这样输出的永远是单例:
1 2
| _container.ComposeExportedValue<DateTime>(DateTime.Now); Console.WriteLine(_container.GetExportedValue<DateTime>());
|
二、元数据和元数据视图
在MEF中,我们可以在导出部件时附加一些数据,而这些附加导出的数据就是元数据,附加导出数据的结构就是元数据视图,这是我觉得MEF中,最令人激动的功能。
导出元数据,我们使用ExportMetadata
特性,设置该特性的Name
和Value
,即可导出对应的元数据,我们以示例来说:
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
即是元数据视图,我们导出的元数据必须满足该接口格式,否则StudentManager
的Improt
就会失败。我们在Student
类上只导出了ClassName
,而我们的元数据视图中还有一个OtherInfo
的属性,这里需要注意一下,如果要提供默认值,必须标记上DefaultValue
,否则如果不赋值(导出)的话,就匹配不了该元数据视图,也就是说,如果我们将DefaultValue
去掉,该程序就不会正确执行了(StudentManager
的Improt
会失败)。
除了使用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具体使用,以及设计层面上的思想,大家一起期待吧。