今天,我們繼續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具體使用,以及設計層面上的思想,大家一起期待吧。