在過去,我們完成一套應用程式後,如果後面對其功能進行了擴充或修整,往往需要重新編譯代碼生成新的應用程式,然後再覆寫原來的程式。這樣的擴充方式對于較小的或者不經常擴充和更新的應用程式來說是可以接受的,而對于像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容器,容器負責把收集到的元件進行合并(組裝)。合并完成後我們就可以使用這些元件了。
本例子的運作結果如下面的截圖所示:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CO2ADM1YWN4ImY3QjNkRDOiJDO0UzY2cTOyEGZ4M2N20CMxITN2ETMy8CX4AzMxAjMvwVO4MzN2MzLcd2bsJ2Lc12bj5ycn9Gbi52YuAzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
從截圖中我們看到,TestWork類的Task1和Task2字段的類型分别為Task_1和Task_2,同時也調用了它們的成員,輸出結果表明,我們之前開發的兩個擴充類Task_1和Task_2已經自動導入到我們目前的應用程式中了。
ApplicationCatalog類是在目前應用程式的運作目錄下查找所有符合要求的exe或dll中的擴充元件,一旦找到就自動收集并生成元件目錄,而後提供給CompositionContainer進行組裝。
從這個例子我們看到MEF架構就像一個大型的組裝廠工廠中的房間,首先設計師們尋找靈感,構思産品的基本模型,這也就是我們所定義的公共接口規範;随後,進行精确計算,進一步把抽象的模型變為具體的工程圖,這相當于我們自己實作編寫的各個擴充類;接着,相關從業人員會把設計師和工程師做好的各個零部件的工程圖收集整理,準備提供給工廠中的房間進行生産組裝,這就相當于我們例子中的ComposablePartCatalog,我們例子中用到的ApplicationCatalog隻是其中一個收集方式,其他的方式還有按程式集進行收集或按特定路徑目錄下的所有類庫進行收集。然後工廠中的房間開始制作并組裝成産品,最終投入使用。
我們可以用下面的圖來描述一下整個過程(此圖純屬虛構,如有雷同,實屬巧合)
現在我們先不必過多關注代碼細節,因為後面我會慢慢介紹,我們隻要明白MEF的用途就可以了。
OK,本文就說到這裡吧,88