程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 實戰MEF(2)導出及導入

實戰MEF(2)導出及導入

編輯:關於C#

上一文中,我們大致明白了,利用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) ;
    
}
    
}
    
}

查看本欄目

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved