前言:最近去了趟外地出差,介紹推廣小組開發的框架類產品。推廣物件是本部門在專案上面的同事——1到2年工作經驗的初級程式設計師。在給他們介紹框架時發現很多框架設計層面的知識他們都沒有接觸過,甚至沒聽說過,這下囧了~~於是乎在想該如何跟他們解釋MEF、AOP、倉儲模式等方面的東東。C#進階系列主要圍繞MEF、AOP、倉儲模式、Automapper、WCF等展開。本篇先來介紹下MEF的基礎知識。
1、什麼是MEF
先來看msdn上面的解釋:MEF(Managed Extensibility Framework)是一個用於建立可擴充套件的輕型應用程式的庫。 應用程式開發人員可利用該庫發現並使用擴充套件,而無需進行配置。 擴充套件開發人員還可以利用該庫輕鬆地封裝程式碼,避免生成脆弱的硬依賴項。 通過 MEF,不僅可以在應用程式內重用擴充套件,還可以在應用程式之間重用擴充套件。
也有人把MEF解釋為“依賴注入”的一種方式,那麼什麼是“依賴注入”?如果這樣解釋,感覺越陷越深……根據博主的理解,瞭解MEF只需要抓住以下幾個關鍵點:
(1)字面意思,可擴充套件的framework,或者叫可擴充套件的庫。也就是說,使用MEF是為了提高程式的可擴充套件性。MEF會根據指定的匯入匯出自動去發現匹配的擴充套件,不需要進行復雜的程式配置。
(2)在設計層面上來說,為什麼要使用MEF?為了“鬆耦合”!我們知道,程式設計有幾個原則,“高內聚,低耦合”就是其中一個。使用MEF可以幫助我們減少內庫之間的耦合。
當然,如果你之前壓根都沒有聽說過MEF,那麼即使看了我上面的解釋,估計也還是雲裡霧裡。沒關係,如果此刻你還有興趣,看了下面的Demo,相信你會有一個初步的認識。
2、為什麼要使用MEF:上面已經解釋過,為了程式的擴充套件和“鬆耦合”。
3、MEF的使用:
(1)MEF基礎匯入匯出的使用:
MEF的使用步驟主要分三步:宿主MEF並組合部件、標記物件的匯出、物件的匯入使用。
我們先來看一個Demo。
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 40 41 42 43 44 45 46 47 48 49 |
class Program2 { //匯入物件使用 [Import("chinese_hello")] public Person oPerson { set; get; } static void Main(string[] args) { var oProgram = new Program2(); oProgram.MyComposePart(); var strRes = oProgram.oPerson.SayHello("李磊"); Console.WriteLine(strRes); Console.Read(); } //宿主MEF並組合部件 void MyComposePart() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //將部件(part)和宿主程式新增到組合容器 container.ComposeParts(this); } } public interface Person { string SayHello(string name); } //宣告物件可以匯出 [Export("chinese_hello", typeof(Person))] public class Chinese : Person { public string SayHello(string name) { return "你好:" + name ; } } [Export("american_hello", typeof(Person))] public class American : Person { public string SayHello(string name) { return "Hello:" + name ; } } |
得到結果:
我們來分析下這段程式碼:
1 2 3 4 5 6 7 8 |
//宿主MEF並組合部件 void MyComposePart() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //將部件(part)和宿主程式新增到組合容器 container.ComposeParts(this); } |
這個方法表示新增當前Program2這個類到組合容器,為什麼要新增到組合容器?是因為只要新增到組合容器中之後,如果該類裡面有Import,MEF才會自動去尋找對應的Export。這也就是為什麼使用MEF前必須要組合部件的原因。
1 2 3 4 5 6 7 8 |
[Export("chinese_hello", typeof(Person))] public class Chinese : Person { public string SayHello(string name) { return "你好:" + name ; } } |
這裡的[Export(“chinese_hello”, typeof(Person))]這個特性表示標記Chinese類的匯出。
將Export轉到定義可以看到:
1 2 3 4 5 6 7 8 9 10 11 |
// // 摘要: // 通過在指定協定名稱下匯出指定型別,初始化 System.ComponentModel.Composition.ExportAttribute 類的新例項。 // // 引數: // contractName: // 用於匯出使用此特性標記的型別或成員的協定名稱,或 null 或空字串 ("") 以使用預設協定名稱。 // // contractType: // 要匯出的型別。 public ExportAttribute(string contractName, Type contractType); |
這裡的兩個引數:第一個表示協定名稱,如果找到名稱相同的Import,那麼就對應當前的Chinese物件;第二個參數列示要匯出的型別。
1 2 |
[Import("chinese_hello")] public Person oPerson { set; get; } |
這裡的chinese_hello是和Export裡面的chinese_hello對應的,由此可知,每一個[Import(“chinese_hello”)]這種Import一定可以找到一個對應的Export,如果找不到,程式就會報異常。當然如果這裡的Import如果改成[Import(“american_hello”)],那麼oPerson肯定就對應一個American物件。
通過上面的程式可以知道,我們使用[Import]這個特性,它的底層其實就是給我們初始化了一個物件。例如上面的[Import(“chinese_hello”)]等價於Person oPerson=new Chinese();。看到這裡可能有人就會說這個Import是多此一舉了,既然我們可以new,為什麼非要用這種奇怪的語法呢,怪彆扭的。其實如果我們站在架構的層面,它的好處就是可以減少dll之間的引用。這個留在下一篇來講。
(2)MEF匯入匯出擴充套件:
按照MEF的約定,任何一個類或者是介面的實現都可以通過[System.ComponentModel.Composition.Export] 屬性將其他定義組合部件(Composable Parts),在任何需要匯入組合部件的地方都可以通過在特定的組合部件物件屬性上使用[System.ComponentModel.Composition.Import ]實現部件的組合,兩者之間通過契約(Contracts)進行通訊。通過上面的例子我們可以知道,物件是可以通過Import和Export來實現匯入和匯出的,那麼我們進一步擴充套件,物件的屬性、欄位、方法、事件等是否也可以通過[ImportAttribute]進行匯入呢?
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 |
class Program2 { [Import("TestProperty")] public string ConsoleTest { get; set; } static void Main(string[] args) { var oProgram = new Program2(); oProgram.MyComposePart(); Console.WriteLine(oProgram.ConsoleTest); Console.Read(); } void MyComposePart() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //將部件(part)和宿主程式新增到組合容器 container.ComposeParts(this); } } public class TestPropertyImport { [Export("TestProperty")] public string TestMmport { get { return "測試屬性可以匯入匯出"; } } } 複製程式碼 |
得到結果:
由此說明,屬性也是可以匯入匯出的。原理與上類似。既然屬性可以,那麼欄位就不用演示了,它和屬性應該是類似的。
下面來看看方法是否可以呢?
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 40 41 42 43 |
class Program2 { [Import("chinese_hello")] public Person oPerson { set; get; } [Import("TestProperty")] public string ConsoleTest { get; set; } [Import("helloname")] public Action<string> TestFuncImport { set; get; } static void Main(string[] args) { var oProgram = new Program2(); oProgram.MyComposePart(); oProgram.TestFuncImport("Jim"); //Console.WriteLine(oProgram.ConsoleTest); //var strRes = oProgram.oPerson.SayHello("李磊"); //Console.WriteLine(strRes); Console.Read(); } void MyComposePart() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //將部件(part)和宿主程式新增到組合容器 container.ComposeParts(this); } } public class TestPropertyImport { [Export("TestProperty")] public string TestMmport { get { return "測試屬性可以匯入匯出"; } } [Export("helloname", typeof(Action<string>))] public void GetHelloName(string name) { Console.WriteLine("Hello:" + name); } } |
由此可知,方法的匯入和匯出是通過匿名委託的方式實現的,那麼由此類推,事件應該也是可以的,有興趣的朋友可以一試。原理和上面是一樣一樣的。
既然屬性、欄位、方法、事件都可以通過Import和Export實現單一物件或變數的匯入和匯出,那麼如果我們想要一次匯入多個物件呢?嘿嘿,微軟總是體貼的,它什麼都為我們考慮到了。我們來看看如何實現。
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 40 41 42 43 44 45 46 |
class Program2 { [ImportMany] public IEnumerable<Person> lstPerson { set; get; } static void Main(string[] args) { var oProgram = new Program2(); oProgram.MyComposePart(); Console.WriteLine(oProgram.lstPerson.Count()); Console.Read(); } void MyComposePart() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //將部件(part)和宿主程式新增到組合容器 container.ComposeParts(this); } } public interface Person { string SayHello(string name); } [Export(typeof(Person))] public class Chinese : Person { public string SayHello(string name) { return "你好:" + name ; } } [Export(typeof(Person))] public class American : Person { public string SayHello(string name) { return "Hello:" + name ; } } |
得到的結果為2。這裡有一點需要注意的,使用ImportMany的時候對應的Export不能有chinese_hello這類string引數,否則lstPerson的Count()為0.
(3)MEF的延遲載入
我們知道,當裝配一個元件的時候,當前元件裡面的所有的Import的變數都自動去找到對應的Export而執行了例項化,有些時候,出於程式效率的考慮,不需要立即例項化物件,而是在使用的時候才對它進行例項化。MEF裡面也有這種延遲載入的機制。
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 40 41 42 43 44 45 46 47 48 49 50 51 |
class Program2 { [Import("chinese_hello")] public Person oPerson { set; get; } [Import("american_hello")] public Lazy<Person> oPerson2 { set; get; } static void Main(string[] args) { var oProgram = new Program2(); oProgram.MyComposePart(); var strRes = oProgram.oPerson.SayHello("李磊"); var strRes2 = oProgram.oPerson2.Value.SayHello("Lilei"); Console.WriteLine(strRes); Console.Read(); } void MyComposePart() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //將部件(part)和宿主程式新增到組合容器 container.ComposeParts(this); } } public interface Person { string SayHello(string name); } [Export("chinese_hello", typeof(Person))] public class Chinese : Person { public string SayHello(string name) { return "你好:" + name ; } } [Export("american_hello", typeof(Person))] public class American : Person { public string SayHello(string name) { return "Hello:" + name ; } } |
通過除錯可知,當程式執行到var strRes = oProgram.oPerson.SayHello(“李磊”);這一行的時候
oPerson物件已經例項化了,而oPerson2.Value物件沒有例項化,當程式執行var strRes2 = oProgram.oPerson2.Value.SayHello(“Lilei”)這一句的時候,oPerson2.Value物件才進行例項化。這種需要在某些對程式效能有特殊要求的情況下面有一定的作用。
講到這裡,我們再來看前面關於理解MEF的兩個關鍵點:
(1)可擴充套件的庫:由於MEF允許通過Import的方式直接匯入物件、屬性、方法等,試想,有人開發了一個元件,他們事先定義好了一系列的匯出(Export),我們只需要將它的元件引進來,使用Import的方式按照他們Export的約定匯入物件即可,不用做其他複雜的配置。
(2)能更好的實現“鬆耦合”:比如我們專案按照面向介面程式設計的方式這樣分層:UI層、BLL介面層、BLL實現層……UI層只需要引用BLL介面層即可,我們在BLL實現層裡面定義好Export的匯出規則,然後再UI層裡面使用Import匯入BLL實現層的物件即可,這樣UI層就不需要新增BLL實現層的引用。減少了dll之間的依賴。
以上就是MEF的一些基礎用法。當然在實際使用中可能不會這麼簡單,但是再複雜的用法都是在這些簡單基礎上面擴充套件起來的。後面還有兩篇會繼續分享MEF在專案設計層面的用法以及帶來的好處。歡迎各位拍磚斧正~~