實戰MEF(2):導出&導入


上一文中,我們大致明白了,利用MEF框架實現自動掃描並組裝擴展組件的思路。本文我們繼續前進,從最初的定義公共接口開始,一步步學會如何使用MEF。

在上一文中我們知道,對於每一個實現了公共規范的擴展組件,都需要進行導出,隨后我們的主應用程序文件中會自動進行組裝。這便產生了一個疑問:為什么需要導出

如果大家還記得,以前我們用VC++寫.dll文件時,都會把需要提供給別人調用的函數標記為導出函數,這樣別人才能調用我們編寫的函數。就好比我們的家,我們一般會有客廳,既然叫客廳,當然是展現給客人看的。有客人來了,我們會在客廳接待,當然我們不願意讓客人進入我們的卧室,那是較為隱私的地方。

因此,對於我們編寫的擴展組件,我們要告訴MEF,哪些類應該被掃描,就像我們的網站一樣,我們會過濾哪些頁面允許搜索引擎進行抓取,一樣的道理。

要把組件標記為可導出類型,需要在類型的定義代碼上附加System.ComponentModel.Composition.ExportAttribute特性。我們可以看看ExportAttribute類的定義。

[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property|AttributeTargets.Field, AllowMultiple = true,

Inherited = false)]

public class ExportAttribute : Attribute

從定義我們看到,ExportAttribute特性可以用於類以及類的成員,能常我們會附加到整個類,以表示整個類型進行導出。

判斷哪個導出類型符合組裝容器導入的條件,是根據ContractName和ContractType屬性。

ContractName我們可以在附加ExportAttribute時指定,也可以不指定。ContractType屬性指定要導出的類型,如果不指定,默認就是當前要導出的類型。比如:

// 公共接口

public interface IMember

{

string GetMemberType();

}

 

[Export]

public class VipMember : IMember

{

public string GetMemberType()

{

return "VIP會員";

}

}

上面的例子,公共接口是IMember,類VipMember實現了該接口並標記為導出類型,但不指定ContractName和ContractType屬性。在這種情況下,默認的協定類型為VipMember,特性附加到哪個類上,默認的導出類型就是該類的類型。

然后,我們再定義一個GenMember類。

[Export]

public class GenMember : IMember

{

public string GetMemberType()

{

return "普通會員";

}

}

這時候,對於GenMember類,導出的類型就是GenMember。

也許大家已經發現,這樣定義導出類型缺點很明顯,即沒有一個通過的協定類型,這樣一來,在組裝擴展組件時就不能做到自動識別了,因為我們每擴展一個類就新一個協定類型(ContractType),這會導致主應用程序的代碼需要反復修改,無法一勞永逸了。所以,通常來說,我們應當把ContractType設置為公共接口的類型,如上面例子中的IMember。故我們應該把代碼改為:

[Export(typeof(IMember))]

public class VipMember : IMember

{

public string GetMemberType()

{

return "VIP會員";

}

}

 

[Export(typeof(IMember))]

public class GenMember : IMember

{

public string GetMemberType()

{

return "普通會員";

}

}

這樣一改,就滿足需求了,只要實現了IMember接口並且附加了ExportAttribute的類型都會被組裝容器自動掃描,哪怕你擴展了99999999999999999999999999999個組件,它都能掃描並組裝。

如果你希望組裝容器在掃描類型時需要特定的類,可以在ExportAttribute中定義ContractName。這樣就使得掃描類型的匹配條件變得更精准,縮小了查找范圍。當然這樣做也降低了智能性,因為在組裝代碼中,你還要去匹配協定名,這也使得主應用程序的代碼會不斷進行修改。

把類型導出之后,就可以提供給組裝容器進行組裝了。就拿我們上面的例子說吧,接下來我們對VipMember和GenMember類進行組裝。

class Program

{

[Import(typeof(IMember))]

public List<IMember> AllMembers;

 

static void Main(string[] args)

{

// 發現類型的方式為當前程序集

AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

// 創建組裝容器

CompositionContainer container = new CompositionContainer(catalog);

Program p = new Program();

// 開始組裝

try

{

container.ComposeParts(p);

Console.WriteLine("---------- 測試調用 ----------");

foreach (IMember m in p.AllMembers)

{

Console.WriteLine(m.GetMemberType());

}

}

catch (CompositionException cex)

{

Console.WriteLine(cex.Message);

}

 

while (Console.ReadKey().Key != ConsoleKey.Escape) ;

}

}

因為我們對IMember擴展了兩個類,為了能讓它們全部導入,在Program類中定義了一個List<IMember>的字段,我們希望把所有導入的類型都放進這個List中。附加ImportAttribute時要與ExportAttribute相對應,前面我們只定義ContractType,所以這里導入時,我們依然使用ContractType來匹配。

這個應用程序看起來似乎沒啥問題,估計可以運行了,於是我們可以按下F5看看結果。

噢,God,居然發生"奇跡"了,從異常信息中我們得知,ImportAttribute不能標記一次性導入多個類型的List的字段。那如何解決呢?莫急莫急,看看以下這個Attribute:

// 摘要:

// 指定屬性、字段或參數應通過 System.ComponentModel.Composition.Hosting.CompositionContainer

// 對象用所有匹配的導出進行填充。

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]

public class ImportManyAttribute : Attribute, IAttributedImport

是的,這個Attribute專用於導入多個類型的,我們只要把代碼改為這樣就行了:

class Program

{

[ImportMany(typeof(IMember))]

public List<IMember> AllMembers;

……

再次運行,我們可以得到預期的結果了。

當今時代,我們什么東西都要綠色環保,我們的程序也不例外。上面的程序看起來是沒什么大問題了。不過,如果我們要導入的類型可能會攜帶一些大型數據,我們要是能讓它們延遲初始化那就節約了一些資源開銷。雖然延遲初始化叫起來不太動聽,不過也不算難以理解的概念,就好像你買體彩中了大獎,但提供方不是直接把一張張鈔票拿給你,可能會先給你支票,然后,你憑支票去銀行提錢。

這個延遲初始化也類似,它在聲明時並不立即初始化,等到你使用的時候才初始化。System.Lazy<T>類將帶領我們走向綠色環保的現代生活,它會等到你訪問其Value屬性時才初始化。於是,我們也把上面的代碼環保一下。

class Program

{

[ImportMany(typeof(IMember))]

public List<Lazy<IMember>> AllMembers;

 

static void Main(string[] args)

{

……

try

{

container.ComposeParts(p);

Console.WriteLine("---------- 測試調用 ----------");

foreach (Lazy<IMember> lz in p.AllMembers)

{

Console.WriteLine(lz.Value.GetMemberType());

}

}

……

最后,提一個不重要的東西,我們代碼中使用的類在

using System.ComponentModel.Composition;

using System.ComponentModel.Composition.Hosting;

請引用System.ComponentModel.Composition(.dll)程序集。

本文實例的完整代碼如下:

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;

using System.Reflection;

 

namespace MEFExam

{

// 公共接口

public interface IMember

{

string GetMemberType();

}

 

[Export(typeof(IMember))]

public class VipMember : IMember

{

public string GetMemberType()

{

return "VIP會員";

}

}

 

[Export(typeof(IMember))]

public class GenMember : IMember

{

public string GetMemberType()

{

return "普通會員";

}

}

 

class Program

{

[ImportMany(typeof(IMember))]

public List<Lazy<IMember>> AllMembers;

 

static void Main(string[] args)

{

// 發現類型的方式為當前程序集

AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

// 創建組裝容器

CompositionContainer container = new CompositionContainer(catalog);

Program p = new Program();

// 開始組裝

try

{

container.ComposeParts(p);

Console.WriteLine("---------- 測試調用 ----------");

foreach (Lazy<IMember> lz in p.AllMembers)

{

Console.WriteLine(lz.Value.GetMemberType());

}

}

catch (CompositionException cex)

{

Console.WriteLine(cex.Message);

}

 

while (Console.ReadKey().Key != ConsoleKey.Escape) ;

}

}

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM