以實(shí)例說話,,一起體驗(yàn)MEF帶來的可擴(kuò)展性吧,Let’s Rock?。,。?/font>
1:新建控制臺(tái)程序SimpleCalculator 在這里要實(shí)現(xiàn)的程序時(shí)SimpleCalculator,,顧名思義:簡單的計(jì)算器,。 所以我們需要定義一個(gè)用來計(jì)算的接口: public interface ICalculator { String Calculate(String input); }
Program 的代碼如下: class Program
MEF所要解決的是尋找插件的功能,傳統(tǒng)的實(shí)現(xiàn)插件的方式主要是使用接口,,即聲明一個(gè)接口,,然后使用配置文件來配置接口使用哪個(gè)實(shí)現(xiàn)類。
微軟知道有這種需求,,于是提供了MEF來實(shí)現(xiàn)插件的功能,。
Composite 原理: 1:聲明一個(gè) CompositionContainer 對(duì)象,這個(gè)對(duì)象里面包含一堆Catalog. 2:這堆Catalog如果是AssemblyCatalog,,則在Assembly中查找,,如果是DirectoryCatalog, 在Directory 中查找,如果即想要在Assembly中查找,,又需要在Directory中查找,, 則采用AggregateCatalog。 3:然后在這堆Catalog中查找與Import 特性相對(duì)應(yīng)的Export標(biāo)記所標(biāo)記的實(shí)現(xiàn)類,,調(diào)用實(shí)現(xiàn)類的構(gòu)造函數(shù)進(jìn)行 Composite(組合),。
知道原理后,你也可以自己實(shí)現(xiàn)自己的CompositionContainer 類了,,
要使用MEF 需要為SimpleCalculator添加 System.ComponentModel.Composition.dll 的引用,, 然后導(dǎo)入命名空間: using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting;
接下來看下Program 的構(gòu)造函數(shù)所做的事情:
聲明一個(gè)AssemblyCatalog,指向Program所在的Assembly. 然后把它添加到 CompositionContainer中,調(diào)用CompositionContainer 的ComposeParts 擴(kuò)展方法,,來Compose(this) 的Parts,。
注:ComposeParts 是擴(kuò)展方法,需要using System.ComponentModel.Composition;
OK,,如何Compose,,在哪個(gè)Assembly中查找實(shí)現(xiàn)類來進(jìn)行Compose已經(jīng)完成了。 目前的問題是:哪些類需要Compose,?,?
為了回答這個(gè)問題,微軟提供了Import和Export特性: Import:哪個(gè)對(duì)象需要Compose,。也就是需要被實(shí)現(xiàn)類給填充,所以Import標(biāo)記的是對(duì)象,,一般該對(duì)象是接口,,因?yàn)槿绻蔷唧w類的話,那還需要Import嗎,? Export:哪個(gè)類可以被用來Compose,也就是說這個(gè)類是不是可以用來填充的實(shí)現(xiàn)類,,所以Export標(biāo)記的是類,而不是具體的某個(gè)對(duì)象,。
所以在這里calculator 使用Import 特性來標(biāo)記: [Import(typeof(ICalculator))] private ICalculator calculator;
接下來MEF 的組合引擎在ComposeParts(this)的時(shí)候,,就會(huì)在catalog 代表的AssemblyCatalog中查找Export特性所修飾的實(shí)現(xiàn)類了,找到實(shí)現(xiàn)類后進(jìn)行Compose,。
如果找不到Export特性修飾的類的話,結(jié)果如下: OK,,接下來添加一個(gè)實(shí)現(xiàn)類,,并使用Export特性來進(jìn)行修飾: [Export(typeof(ICalculator))] public class MySimpleCalculator : ICalculator { public string Calculate(string input) { return "MySimpleCalculator 處理了" + input; } }
運(yùn)行結(jié)果如下: 當(dāng)然Import和Export還提供了其他的構(gòu)造函數(shù),所以你還可以將上面的Import和Export修改為: [Import("calculator1", typeof(ICalculator))] [Export("calculator1", typeof(ICalculator))]
之所以提供ContractName為calculator1 是因?yàn)槟憧赡苡卸鄠€(gè)ICalculator對(duì)象需要填充,。
修改Program的代碼如下: class Program { private CompositionContainer _container;
[Import("calculator1", typeof(ICalculator))] private ICalculator calculator1;
[Import("calculator2", typeof(ICalculator))] private ICalculator calculator2;
public Program() { //var catalog = new AggregateCatalog(); //catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
var catalog = new AssemblyCatalog(typeof(Program).Assembly); _container = new CompositionContainer(catalog);
try { this._container.ComposeParts(this); } catch(CompositionException compositionException) { Console.WriteLine(compositionException.ToString()); } }
static void Main(string[] args) { Program p = new Program(); string s; Console.WriteLine("Enter Command:");
while (true) { s = Console.ReadLine(); Console.WriteLine(p.calculator1.Calculate(s)); Console.WriteLine(p.calculator2.Calculate(s)); } } }
修改Export修飾的類為: [Export("calculator1", typeof(ICalculator))] public class MySimpleCalculator1 : ICalculator { public string Calculate(string input) { return "第一個(gè)Calculator 處理了" + input; } }
[Export("calculator2", typeof(ICalculator))] public class MySimpleCalculator2 : ICalculator { public string Calculate(string input) { return "第二個(gè)Calculator 處理了" + input; } }
運(yùn)行結(jié)果如下:
因?yàn)镮mport和Export是一一對(duì)應(yīng)的,,在現(xiàn)實(shí)世界中,存在著大量一對(duì)多的情況,,微軟也預(yù)料到了這種情況,,所以提供了ImportMany 特性。
在上個(gè)例子中的MySimpleCalculator的Calculate方法返回的是一句話,,在這個(gè)例子中要真正實(shí)現(xiàn)計(jì)算的功能,,例如輸入5+3,輸出8,,輸入7*4,,輸出28。
為了支持 + - * / 四種Operation.所以在MySimpleCalculator中聲明一個(gè)operations 的列表,。 [Export(typeof(ICalculator))] class MySimpleCalculator : ICalculator { [ImportMany] IEnumerable<Lazy<IOperation, IOperationData>> operations;
public string Calculate(string input) { return "calculate 處理了" + input; } }
之所以在MySimpleCalculator 中聲明operations ,,是因?yàn)槭怯?jì)算器支持多種運(yùn)算。因?yàn)?/span>operations 需要多個(gè)operation 來Compose(填充),所以使用ImportMany特性來修飾,,和Import特性一樣,,ImportMany特性一般也是修飾接口。
Ioperation 和IOperationData的定義如下:
public interface IOperation { int Operate(int left, int right); }
public interface IOperationData { Char Symbol { get; } }
Lazy<IOperation, IOperationData> operations: 提供對(duì)對(duì)象及其關(guān)聯(lián)的元數(shù)據(jù)的延遲間接引用,,以供 Managed Extensibility Framework 使用,。 意思是說IOperation 和IOperationData之間的引用需要延遲,為什么需要延遲,?,,因?yàn)?/span>IOperation需要根據(jù)IOperationData的Symbol符號(hào)來延遲創(chuàng)建。 也就是說,,如果IOperationData的Symbol 等于 “+”,那么IOperation對(duì)象是AddOperation.如果IOperationData的Symbol等于”-”,那么IOperation對(duì)象是SubtractOperation. 那么如何保證這點(diǎn)呢,?
關(guān)鍵點(diǎn)就在于ExportMetadata attribute 上,。 看下Add Operation 的定義:
[Export(typeof(IOperation))] [ExportMetadata("Symbol", '+')] class Add : IOperation { public int Operate(int left, int right) { return left + right; } }
在這里ExportMetadata特性的Symbol 為+。所以當(dāng)IOperationData的Symbol為”+” 的時(shí)候,,匹配的就是Add Operation
MySimpleCalculator 的完整代碼如下:
[Export(typeof(ICalculator))] class MySimpleCalculator : ICalculator { [ImportMany] IEnumerable<Lazy<IOperation, IOperationData>> operations;
public string Calculate(string input) { int left; int right;
char operation;
int fn = FindFirstNonDigitPosition(input);
if (fn < 0) return "Could not parse command.";
try { left = int.Parse(input.Substring(0, fn)); right = int.Parse(input.Substring(fn + 1)); } catch { return "Could not parse command"; }
operation = input[fn];
foreach (Lazy<IOperation, IOperationData> i in operations) { if (i.Metadata.Symbol.Equals(operation)) return i.Value.Operate(left, right).ToString(); }
return "Operation Not Found!"; }
private int FindFirstNonDigitPosition(string s) { for (int i = 0; i < s.Length; i++) { if (!(Char.IsDigit(s[i]))) return i; } return -1; } }
回頭再看看上例的Program代碼: class Program { private CompositionContainer _container;
[Import(typeof(ICalculator))] private ICalculator calculator;
public Program() { //var catalog = new AggregateCatalog(); //catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
var catalog = new AssemblyCatalog(typeof(Program).Assembly);
_container = new CompositionContainer(catalog);
try { this._container.ComposeParts(this); } catch(CompositionException compositionException) { Console.WriteLine(compositionException.ToString()); } }
static void Main(string[] args) { Program p = new Program(); string s; Console.WriteLine("Enter Command:");
while (true) { s = Console.ReadLine(); Console.WriteLine(p.calculator.Calculate(s)); } } }
當(dāng)this._container.ComposeParts(this); 的時(shí)候,,MEF組合引擎就開始對(duì)標(biāo)記了Import特性的接口進(jìn)行Compose,所以在這里是calculator,。在哪里找實(shí)現(xiàn)類呢,?,AssemblyCatalog表明在Program的當(dāng)前Assembly中查找實(shí)現(xiàn)類,,所以找到了MySimpleCalculator在構(gòu)造MySimpleCalculator 的時(shí)候,,發(fā)現(xiàn)了ImportMany特性修飾的operations。于是繼續(xù)在AssemblyCatalog中找到了Add,。
上面的過程是Compose的過程,。 那么MySimpleCalculator 如何進(jìn)行Calculate的呢? 例如5+3 1:找出第一個(gè)非數(shù)字的位置,,也就是需要找出 +,。 2:聲明left,right.并且left 為5,right為3. 3:根據(jù)符號(hào)+來構(gòu)造IOperation對(duì)象,,接著調(diào)用IOperation對(duì)象的Operate(left,right)方法,。 foreach (Lazy<IOperation, IOperationData> i in operations) { if (i.Metadata.Symbol.Equals(operation)) return i.Value.Operate(left, right).ToString(); }
運(yùn)行結(jié)果: 因?yàn)槟壳岸x了Add 的Operation。所以根據(jù)符號(hào)+ 能夠找到Add,,但是*我們沒有定義,,所以Operation Not Found!.
于是開始定義Multiple: [Export(typeof(IOperation))] [ExportMetadata("Symbol", '*')] class Multiple : IOperation { public int Operate(int left, int right) { return left * right; } }
再次運(yùn)行,結(jié)果如下:
當(dāng)然還可以在當(dāng)前程序集下面增加- ,/,^,% 等Operation,。
為了讓事情更加的有趣,,我打算在Debug目錄下增加一個(gè)目錄CalculateExtensions,然后將-,/ ..的Operation放到里面來,,讓MEF自動(dòng)發(fā)現(xiàn),。
首先新建類庫項(xiàng)目:SimpleCalculatorExtension 因?yàn)樾枰獙?shí)現(xiàn)IOperation ,所以需要添加對(duì)SimpleCalculator項(xiàng)目的引用,。 因?yàn)樾枰?/span>Export特性,,所以需要添加對(duì)System.ComponentModel.Composition的引用。 整個(gè)項(xiàng)目的結(jié)果如下:
Subtract代碼如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel.Composition;
namespace SimpleCalculatorExtension { [Export(typeof(SimpleCalculator.IOperation))] [ExportMetadata("Symbol", '-')] class Subtract : SimpleCalculator.IOperation { public int Operate(int left, int right) { return left - right; } } }
生成成功后,,將SimpleCalculatorExtension.dll 拷貝到CalculateExtensions目錄下: 現(xiàn)在SimpleCalculator的Debug目錄應(yīng)該是這樣,。
并且CalculateExtensions文件夾下面有SimpleCalculatorExtension.dll.
接下來唯一要修改的是Program的catalog 對(duì)象。 為了讓catalog既支持在Program的Assembly中查找,,又支持在CalculateExtensions目錄下查找,。修改代碼如下: public Program() { var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly)); catalog.Catalogs.Add(new DirectoryCatalog("CalculateExtensions"));
_container = new CompositionContainer(catalog);
try { this._container.ComposeParts(this); } catch(CompositionException compositionException) { Console.WriteLine(compositionException.ToString()); } }
運(yùn)行結(jié)果如下:
修改SimpleCalculatorExtension 的Subtract方法為: namespace SimpleCalculatorExtension { [Export(typeof(SimpleCalculator.IOperation))] [ExportMetadata("Symbol", '-')] class Subtract : SimpleCalculator.IOperation { public int Operate(int left, int right) { Console.WriteLine("SimpleCalculatorExtension的方法"); return left - right; } } } 重新生成SimpleCalculatorExtension.dll 然后拷貝到CalculateExtensions 文件夾下: 再次運(yùn)行程序,輸出入下: |
|