在過去,我們完成一套應用程序後,如果後面對其功能進行了擴展或修整,往往需要重新編譯代碼生 成新的應用程序,然後再覆蓋原來的程序。這樣的擴展方式對於較小的或者不經常擴展和更新的應用程 序來說是可以接受的,而對於像ERP系統那樣復雜而且常常需要擴展的應用程序,這種擴展方法就不夠方 便,因為每次都要修改源代碼或重新引用組件。
尤其是組件(許多dll),如果每編寫一個新組 件又要在主項目中引用一次,顯然主項目就不得不經常重新生成。要是能有一種機制,可以在主項目應 用程序不作任何修改就可以自動識別並擴展組件,就會很便捷,我們每次擴展只需要更新或者添加某些 dll文件即可。
MEF正是為了解決上述問題而誕生。MEF全稱Managed Extensibility Framework, 至於如何翻譯不重要,你喜歡怎麼個譯法都無所謂,我們只要明白它用來干啥就好了。
寬泛的理 論似乎作用不明顯,我們還是先來弄一個簡單的例子。現在假設我在開發一個應用程序,首先我要為一 些組件以及將來可以要擴展的組件定義公共接口(或者說是協定,大家是否記得在WCF中也是這樣,先定 義一些公共的服務協定,然後視具體情況對這些協定進行擴展),然後我可以按照不同的情形去實現這 些接口,這也是我們常說的,接口可以起到規范作用,有了規范,正是為後期擴展打下可行性基礎。
例子的主項目是一個控制台應用程序,我們先在解決方案中添加一個類庫項目,為了簡單演示, 我定義了以下接口:
public interface IExtBase { void DoTask(); string TaskName { get; } }
這個IExtBase接口就作為我們要擴展的組件的公共協定,不管我以後怎麼擴展,哪怕我要添 加100000個組件,這些組件都要實現IExtBase接口。
這裡我做了兩個擴展作為例子,為了表明 MEF框架能自動發現組件,我把兩個實現IExtBase接口的類寫到另外一個類庫項目中——TaskToa.dll。
[Export("task1", typeof(CommExtBase.IExtBase))] public class Task_1 : CommExtBase.IExtBase { public void DoTask() { Console.WriteLine("任務1執行。"); } public string TaskName { get { return "任務1"; } } } [Export("task2", typeof(CommExtBase.IExtBase))] public class Task_2 : CommExtBase.IExtBase { public void DoTask() { Console.WriteLine("任務2執行。"); } public string TaskName { get { return "任務2"; } } }
附加ExportAttribute特性用於擴展的組件類,表示它們將被導出,導出的類型會被MEF自動 發現。
在主項目中我們不引用這個TaskToa類庫,先把TaskToa項目生成一個TaskToa.dll,直接 復制到.exe應用程序的動行目錄下,在調試模中為\\項目\\bin\\Debug目錄下。
由於實現公共接口的類不止一個,後續可能還有10000000個,為了能夠使所有的擴展組件都能被發 現,統一的協定類型為IExtBase接口(與WCF的實現服務協定相似),在附加ExportAttribute特性時指 定了每個組件類的協定名,而協定類型都是IExtBase接口,協定類型必須統一才能保證所有擴展的類能 被MEF框架發現。
最後在.exe主項目的代碼中加入以下代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; namespace MainApp { class TestWork { [Import("task1")] public CommExtBase.IExtBase Task1; [Import("task2")] public CommExtBase.IExtBase Task2; } class Program { static void Main(string[] args) { ApplicationCatalog appCat = new ApplicationCatalog(); CompositionContainer container = new CompositionContainer(appCat); TestWork tw = new TestWork(); try { container.ComposeParts(tw); Console.WriteLine("Task1的類型:{0}\tTaskName: {1}\t調用DoTask方法:",tw.Task1.GetType().Name,tw.Task1.TaskName); tw.Task1.DoTask(); Console.Write("\n\n"); Console.WriteLine("Task2的類型:{0}\tTaskName:{1}\t調用DoTask方法:", tw.Task2.GetType().Name, tw.Task2.TaskName); tw.Task2.DoTask(); } catch (CompositionException cex) { Console.WriteLine(cex.Message); } Console.Read(); } } }
TestWork類用來包裝最後被合並的組件,它有兩個公共字段,類型雖然都是IExtBase,但由於應用 了ImportAttribute特性,並且指定了協定名,這些協定名一定要與我們之前在擴展類中應用 ExportAttribute是指定的協定名相對應。附加了ImportAttribute特性可以讓MEF識別對應的組件並導入 到TestWork類中。
在Main入口點中,我們先使用ApplicationCatalog類來收集所有可用的擴展組 件,然後把收集到的信息傳給CompositionContainer容器,容器負責把收集到的組件進行合並(組裝) 。合並完成後我們就可以使用這些組件了。
本例子的運行結果如下面的截圖所示:
從截圖中我們看到,TestWork類的Task1和Task2字段的類型分別為Task_1和Task_2,同時也調 用了它們的成員,輸出結果表明,我們之前開發的兩個擴展類Task_1和Task_2已經自動導入到我們當前 的應用程序中了。
ApplicationCatalog類是在當前應用程序的運行目錄下查找所有符合要求的 exe或dll中的擴展組件,一旦找到就自動收集並生成組件目錄,而後提供給CompositionContainer進行 組裝。
從這個例子我們看到MEF框架就像一個大型的組裝廠車間,首先設計師們尋找靈感,構思 產品的基本模型,這也就是我們所定義的公共接口規范;隨後,進行精確計算,進一步把抽象的模型變 為具體的工程圖,這相當於我們自己實現編寫的各個擴展類;接著,相關工作人員會把設計師和工程師 做好的各個零部件的工程圖收集整理,准備提供給車間進行生產組裝,這就相當於我們例子中的 ComposablePartCatalog,我們例子中用到的ApplicationCatalog只是其中一個收集方式,其他的方式還 有按程序集進行收集或按特定路徑目錄下的所有類庫進行收集。然後車間開始制作並組裝成產品,最終 投入使用。
我們可以用下面的圖來描述一下整個過程(此圖純屬虛構,如有雷同,實屬巧合)
現在我們先不必過多關注代碼細節,因為後面我會慢慢介紹,我們只要明白MEF的用途就可以了。
OK,本文就說到這裡吧,88
查看本欄目