MEF核心笔记(4)细说MEF中的Attribute [下]
2013-04-09 21:29 Sun.M 阅读(2487) 评论(3) 编辑 收藏 举报今天,我们继续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 |
只有满足上面这张表,导入导出才会匹配,下面我们做一个很简单的示例:
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; } } }
最后输出的是一个false和true,看懂了这个示例,你就看懂整个创建规则了,如果没有看懂,抱歉,只能说明,我的示例写得太烂了。
此外,我们可以预先在容器中定义导出,这样的定义不需要【Export】特性描述类型,并且这样输出的永远是单例:
_container.ComposeExportedValue<DateTime>(DateTime.Now); Console.WriteLine(_container.GetExportedValue<DateTime>());
二、元数据和元数据视图
在MEF中,我们可以在导出部件时附加一些数据,而这些附加导出的数据就是元数据,附加导出数据的结构就是元数据视图,这是我觉得MEF中,最令人激动的功能。
导出元数据,我们使用【ExportMetadata】特性,设置该特性的【Name】和【Value】,即可导出对应的元数据,我们以示例来说:
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】特性,以上的示例,我们可以改成这样:
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】的方法,即通知组件部件组装的地方。请看简单的示例:
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具体使用,以及设计层面上的思想,大家一起期待吧。