前言
Managed Extensibility Framework(MEF)是.NET平臺下的一個擴充套件性管理框架,它是一系列特性的集合,包括依賴注入(DI)等。MEF為開發人員提供了一個工具,讓我們可以輕鬆的對應用程式進行擴充套件並且對已有的程式碼產生最小的影響,開發人員在開發過程中根據功能要求定義一些擴充套件點,之後擴充套件人員就可以使用這些擴充套件點與應用程式互動;同時MEF讓應用程式與擴充套件程式之間不產生直接的依賴,這樣也允許在多個具有同樣的擴充套件需求之間共享擴充套件程式。
MEF方式
MEF 提供一種通過“組合”隱式發現元件的方法。 MEF 元件(稱為“部件-Part”)。部件以宣告方式同時指定其依賴項(稱為“匯入-Import”)及其提供的功能(稱為“匯出-Export”)。
MEF原理上很簡單,找出有共同介面的匯入、匯出。然後找到把匯出的例項化,賦給匯入。說到底MEF就是找到合適的類例項化,把它交給匯入。
使用 MEF 編寫的可擴充套件應用程式會宣告一個可由擴充套件元件填充的匯入,而且還可能會宣告匯出,以便向擴充套件公開應用程式服務。 每個擴充套件元件都會宣告一個匯出,而且還可能會宣告匯入。 通過這種方式,擴充套件元件本身是自動可擴充套件的。
如何宣告一個部件-匯入和匯出
匯出”是部件向容器中的其他部件提供的一個值,而“匯入”是部件向要通過可用匯出滿足的容器提出的要求。 在特性化程式設計模型中,匯入和匯出是由修飾類或成員使用 Import 和Export 特性宣告的。 Export 特性可修飾類、欄位、屬性或方法,而 Import 特性可修飾欄位、屬性或建構函式引數。為了使匯入與匯出匹配,匯入和匯出必須具有相同的協定。
假設有一個類HomeController,它宣告瞭可以匯入外掛的型別是ITestRepository。
public class HomeController : Controller { [Import] public ITestRepository Repository { get; set; } }
這裡有一個類,它宣告為匯出。型別同樣為ITestRepository。
[Export(typeof(ITestRepository))] public class TestRepository:ITestRepository { public string GetTestString() { return "Hello World"; } }
這樣我們使用Repository屬性的時候就可以獲得到TestRepository的例項。
如何匯入多個部件
一般的 ImportAttribute 特性由一個且只由一個 ExportAttribute 填充。 如果有多個匯出可用,則組合引擎將生成錯誤。若要建立一個可由任意數量的匯出填充的匯入,可以使用 ImportManyAttribute 特性。
一個介面
public interface ITestRepository { string GetTestString(); }
兩個實現
[Export(typeof(ITestRepository))] public class TestRepository:ITestRepository { public string GetTestString() { return "Hello World"; } }
[Export(typeof(ITestRepository))] class TextRepository:ITestRepository { public string GetTestString() { return "Hello World Text"; } }
[ImportMany] public IEnumerable<ITestRepository> Repository { get; set; }
這樣呼叫Repository就可以進行選擇了
匯入和匯出的繼承
如果某個類繼承自部件,則該類也可能會成為部件。 匯入始終由子類繼承。 因此,部件的子類將始終為部件,並具有與其父類相同的匯入。通過使用 Export 特性的宣告的匯出不會由子類繼承。 但是,部件可通過使用 InheritedExport 特性繼承自身。 部件的子類將繼承並提供相同的匯出,其中包括協定名稱和協定型別。 與 Export 特性不同,InheritedExport 只能在類級別(而不是成員級別)應用。 因此,成員級別匯出永遠不能被繼承。
下面四個類演示了匯入和匯出繼承的原則。 NumTwo 繼承自 NumOne,因此 NumTwo 將匯入 IMyData。 普通匯出不會被繼承,因此 NumTwo 將不會匯出任何內容。 NumFour 繼承自NumThree。 由於 NumThree 使用了 InheritedExport,因此 NumFour 具有一個協定型別為 NumThree 的匯出。 成員級別匯出從不會被繼承,因此不會匯出 IMyData。
[Export] public class NumOne { [Import] public IMyData MyData { get; set; } } public class NumTwo : NumOne { //匯入會被繼承,所以NumTwo會有匯入屬性 IMyData //原始的匯出不能被繼承,所以NumTwo不會有任何匯出。所以它不會被目錄發現 } [InheritedExport] public class NumThree { [Export] Public IMyData MyData { get; set; } //這個部件提供兩個匯出,一個是NumThree,一個是IMyData型別的MyData } public class NumFour : NumThree { //因為NumThree使用了InheritedExport特性,這個部件有一個匯出NumThree。 //成員級別的匯出永遠不會被繼承,所以IMydata永遠不是匯出 }
建立策略
當部件指定執行匯入和組合時,組合容器將嘗試查詢匹配的匯出。 如果它將匯入與匯出成功匹配,則匯入成員將設定為匯出的物件的例項。 匯出部件的建立策略控制此例項來源於何處。匯入和匯出都可從值 Shared、NonShared 或 Any 中指定部件的建立策略。 匯入和匯出的預設值均為 Any。
例如:
[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
宣告週期和釋放
由於部件承載於組合容器中,因此其生命週期可能比普通物件更復雜。需要在關閉時執行工作的部件和需要釋放資源的部件應照常為 .NET Framework 物件實現 IDisposable。 但是,由於容器建立並維護對部件的引用,因此只有擁有部件的容器才應對其呼叫 Dispose 方法。 容器本身實現 IDisposable,並且作為 Dispose 中其清理的一部分,它將對擁有的所有部件呼叫 Dispose。 因此,當不再需要組合容器及其擁有的任何部件時,您應始終釋放該組合容器。
對於生存期很長的組合容器,建立策略為“非共享”的部件的記憶體消耗可能會成為問題。 這些非共享部件可以多次建立,並且在容器本身被釋放之前將不會得到釋放。 為了應對這種情況,容器提供了 ReleaseExport 方法。 如果對非共享匯出呼叫此方法,將會從組合容器中移除該匯出並將其釋放。 僅由移除的匯出使用的部件以及樹中更深層的諸如此類部件將也會被移除並得到釋放。 通過這種方式,不必釋放組合視窗本身即可回收資源。