MEF核心筆記(3)細說MEF中的Attribute [上]


又到了寫筆記的時候了,這次的內容網羅了MEF中的所有Attribute,感覺內容偏多,所以分為兩個篇幅來記錄,篇幅內容過多的話,感覺不太適合閱讀。

本篇記錄包括以下內容:

 

一、基本導入導出

基本導入和導出在前兩篇博客中都有涉及到,這里再簡單的補充一下。

最基本的導入導出

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();
    }
}

五、總結

到總結了,這次內容還是蠻多的,不過很基礎,也不是很難理解,不過是導入導出而已。我們熟悉並且了解了導入導出的種類,也了解了導入導出的一些特性,假以時日,我們一定能成為導入導出的一等一高手!

這次就不提供源碼了,都在筆記里了。


免責聲明!

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



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