又到了寫筆記的時候了,這次的內容網羅了MEF中的所有Attribute,感覺內容偏多,所以分為兩個篇幅來記錄,篇幅內容過多的話,感覺不太適合閱讀。
本篇記錄包括以下內容:
- 基本導入導出(ExportAttribute、ImportAttribute)
- 導入導出的種類(ImportingConstructorAttribute、ImportManyAttribute)
- 導入和導出的繼承(InheritedExportAttribute)
- 不被發現的導出(PartNotDiscoverableAttribute)
- 總結
一、基本導入導出
基本導入和導出在前兩篇博客中都有涉及到,這里再簡單的補充一下。
最基本的導入導出
public interface IAction { void DoAction(); }
上面這個接口是一個契約(Contract),會一直貫穿本篇文章的示例代碼中。
[Export(typeof(IAction))] public class MyAction : IAction { public void DoAction() { Console.WriteLine("My Action Invoked"); } } public class ActionManager { [Import(typeof(IAction))] private IAction _action; }
這便是最最基本的導入和導出,我們都約定了IAction作為契約,所以這樣的導入導出可以得到匹配。
默認導入導出類型
如果上述代碼改變一下,成為下面這樣:
[Export("IAction")] public class MyAction : IAction { public void DoAction() { Console.WriteLine("My Action Invoked"); } } public class ActionManager { [Import("IAction")] private IAction _action; public void DoAction() { _action.DoAction(); } }
此時我們都只設定了ContractName,這個屬性,並且一樣,但是,這樣的導入導出是不會匹配的,因為Export默認的導出類型(ContractType)是MyAction,而Import默認需要的導入類型是IAction,所以不會匹配。所以,我們改變下代碼:
[Import("IAction",typeof(MyAction))]
這樣,便可以得到匹配了。
二、導入導出的種類
在MEF中,我們可以導入和導出各種種類,以下羅列出來,方便以后使用。
導入導出字段
[Export(typeof(IAction))] private IAction MyExportAction = new MyAction();
[Export] public class ActionManager { [Import(typeof(IAction))] private IAction _action; public void DoAction() { _action.DoAction(); } }
在ActionManager上面標記了Export特性,這樣,我們可以很方便的用下面的代碼來實例化:
var actionManager = _container.GetExportedValue<ActionManager>();
基本上,我們是用MEF的話,都會采用這樣的方式來創建部件。
導入導出屬性
這個與上面的導入導出字段類似,不作太多贅述了。
[Export(typeof(IAction))] private static IAction MyExportAction { get; set; }
導入導出方法
public class MyAction { [Export(typeof(Action))] public void DoAction() { Console.WriteLine("My Action Invoked"); } } [Export] public class ActionManager { [Import(typeof(Action))] public Action _importAction; public void DoAction() { _importAction(); } }
實際上,我么導入導出的是委托類型(delegate)。
導入構造函數
[Export(typeof(IAction))] public class MyAction : IAction { public void DoAction() { Console.WriteLine("My Action Invoked"); } } [Export] public class ActionManager { private IAction _action; [ImportingConstructor] public ActionManager(IAction action) { _action = action; } public void DoAction() { _action.DoAction(); } }
我們使用了ImportingConstructor特性來完成構造函數的導入。
動態導入
所謂的動態導入,就是指我們需要導入的字段或屬性是dynamic類型的,當然,這是.NET 4之后特有的。
public class ActionManager { [Import("Action")] public dynamic MyAction { get; set; } } [Export("Action", typeof(IAction))] public class MyAction : IAction { } [Export("Action")] public class MyAction2 { }
需要注意的是,上面兩個導出都會匹配,因為dynamic沒有類型約束,除非指定ContractType。
延時導入
延時導入,即並非立即導入組合,而是在訪問時進行實例化,下面是一個完整的綜合的例子。
namespace MEFTest { class Program { //字段導出 [Export] private static IAction MyExportedAction; private static CompositionContainer _container; static void Main(string[] args) { var catalog = new AssemblyCatalog(typeof(Program).Assembly); _container = new CompositionContainer(catalog); //此時MyExportedAction字段是NULL var actionManager = _container.GetExportedValue<ActionManager>(); //如果這時候直接 actionManager.DoAction(); 的話,會報錯 //或者ActionManager中的導入不是Lazy的話,也會報錯 MyExportedAction = new MyAction();//我們實例化一下 actionManager.DoAction(); while (true) { Console.ReadLine(); } } } public interface IAction { void DoAction(); } public class MyAction : IAction { public void DoAction() { Console.WriteLine("My Action Invoked"); } } [Export] public class ActionManager { private Lazy<IAction> _action; //構造延時導入 [ImportingConstructor] public ActionManager(Lazy<IAction> action) { _action = action; } public void DoAction() { _action.Value.DoAction(); } } }
導入多個對象
需要導入多個對象(即集合)時,我們使用ImportMany特性,使用此特性,我們可以將所有匹配的導出,導入到一個集合中,可以使用IEnumerable<T>也可以使用數組,當然,最好是配合Lazy一起使用,這樣我們無需實例化所有的導出。
[ImportMany(typeof(IAction))] private IEnumerable<IAction> _actions; [ImportMany(typeof(IAction))] private Lazy<IAction>[] _lazyActions;
值得注意的是,我們在導入構造函數時,即ImportingConstructor,如果構造函數的參數是IEumerable<T>類型,使用ImportingConstructor導入的話,回去尋找IEumerable<T>類型的導出,而不是T類型的一組導出,如果我們需要的是T類型的一組導出的話,可以使用下面的代碼:
[Export] public class ActionManager { private IEnumerable<Lazy<IAction>> _actions; [ImportingConstructor] public ActionManager([ImportMany]IEnumerable<Lazy<IAction>> actions) { _actions = actions; } public void DoAction() { foreach (var action in _actions) { action.Value.DoAction(); } } }
必備導入和可選導入
必備和可選是相對於組合引擎(一般都是Container)創建部件時而言,例如默認的情況下,組合引擎創建部件都是使用無參數的構造函數,而使用了ImportingConstructor特性后,則會使用由ImportingConstructor所描述的構造參數,此時該項導入便是必備導入;可選導入就是在組裝失效(未匹配到)的情況下,采用默認值,則此項導入為可選導入,例如:
[Import(typeof(IAction), AllowDefault = true)] public IAction _myAction;
使用AllowDefault為true的情況下,該導入便是可選導入,如果匹配不上,上訴示例的字段值為空(null)。
三、導入和導出的繼承
在MEF中,Export特性是不會被繼承的,如果希望子類繼承父類的導出,則要使用InheritedExport特性;而Import特性是繼承的,也就是說,如果父類中引入了某個導入,則子類中依然會引用該導入。我們看示例:
[InheritedExport] public abstract class ActionManager { [Import] private Lazy<IAction> _action; public void DoAction() { _action.Value.DoAction(); } } public class ChildActionManager : ActionManager { }
此示例是根據上面的完整示例改寫而來,我們這樣調用它:
var actionManager = _container.GetExportedValue<ActionManager>();
此時我們調用了的是ChildActionManager,因為它的父類用了InheritedExport,並且父類是抽象類,具體內容,下一節中會講解。
四、不被發現的導出
在上面的示例中,我們用了abstract,也就是抽象類,如果不用抽象類的話,上訴的示例便會失敗,報錯內容會說找到多個匹配的導出。因為ActionManager會導出ActionManager,它的子類ChildActionManager也會導出ActionManager,所以GetExportedValue<T>方法會出錯。
經過上面的示例,我們已經知道了,如果類是抽象的,則它的導出是不會被發現的,但,如果我們無法使得類變成抽象類,又不想它導出被發現呢?我們可以使用PartNotDiscoverable特性來描述,這樣,我們改寫上面的示例為下,一切會正常執行:
[InheritedExport] [PartNotDiscoverable] public class ActionManager { [Import] private Lazy<IAction> _action; public void DoAction() { _action.Value.DoAction(); } }
五、總結
到總結了,這次內容還是蠻多的,不過很基礎,也不是很難理解,不過是導入導出而已。我們熟悉並且了解了導入導出的種類,也了解了導入導出的一些特性,假以時日,我們一定能成為導入導出的一等一高手!
這次就不提供源碼了,都在筆記里了。