MEF核心笔记(3)细说MEF中的Attribute [上]

又到了写笔记的时候了,这次的内容网罗了MEF中的所有Attribute,感觉内容偏多,所以分为两个篇幅来记录,篇幅内容过多的话,感觉不太适合阅读。

一、基本导入导出

基本导入和导出在前两篇博客中都有涉及到,这里再简单的补充一下。

最基本的导入导出

1
public interface IAction {
    void DoAction();
}

上面这个接口是一个契约(Contract),会一直贯穿本篇文章的示例代码中。

1
[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作为契约,所以这样的导入导出可以得到匹配。

默认导入导出类型

如果上述代码改变一下,成为下面这样:

1
[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,所以不会匹配。所以,我们改变下代码:

1
[Import("IAction",typeof(MyAction))]

这样,便可以得到匹配了。

二、导入导出的种类

在MEF中,我们可以导入和导出各种种类,以下罗列出来,方便以后使用。

导入导出字段

1
[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特性,这样,我们可以很方便的用下面的代码来实例化:

1
var actionManager = _container.GetExportedValue<ActionManager>();

基本上,我们是用MEF的话,都会采用这样的方式来创建部件。

导入导出属性

这个与上面的导入导出字段类似,不作太多赘述了。

1
[Export(typeof(IAction))]
private static IAction MyExportAction { get; set; }

导入导出方法

1
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)。

导入构造函数

1
[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.0之后特有的。

1
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

延时导入

延时导入,即并非立即导入组合,而是在访问时进行实例化,下面是一个完整的综合的例子。

1
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一起使用,这样我们无需实例化所有的导出。

1
[ImportMany(typeof(IAction))]
private IEnumerable<IAction> _actions;

[ImportMany(typeof(IAction))]
private Lazy<IAction>[] _lazyActions;

值得注意的是,我们在导入构造函数时,即ImportingConstructor,如果构造函数的参数是IEumerable类型,使用ImportingConstructor导入的话,回去寻找IEumerable类型的导出,而不是T类型的一组导出,如果我们需要的是T类型的一组导出的话,可以使用下面的代码:

1
[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所描述的构造参数,此时该项导入便是必备导入;可选导入就是在组装失效(未匹配到)的情况下,采用默认值,则此项导入为可选导入,例如:

1
[Import(typeof(IAction), AllowDefault = true)]
public IAction _myAction;

使用AllowDefaulttrue的情况下,该导入便是可选导入,如果匹配不上,上诉示例的字段值为空(null)。

三、导入和导出的继承

在MEF中,Export特性是不会被继承的,如果希望子类继承父类的导出,则要使用InheritedExport特性;而Import特性是继承的,也就是说,如果父类中引入了某个导入,则子类中依然会引用该导入。我们看示例:

1
[InheritedExport]
public abstract class ActionManager {
    [Import]
    private Lazy<IAction> _action;

    public void DoAction() {
        _action.Value.DoAction();
    }
}

public class ChildActionManager : ActionManager { }

此示例是根据上面的完整示例改写而来,我们这样调用它:

1
var actionManager = _container.GetExportedValue<ActionManager>();

此时我们调用了的是ChildActionManager,因为它的父类用了InheritedExport,并且父类是抽象类,具体内容,下一节中会讲解。

四、不被发现的导出

在上面的示例中,我们用了abstract,也就是抽象类,如果不用抽象类的话,上诉的示例便会失败,报错内容会说找到多个匹配的导出。因为ActionManager会导出ActionManager,它的子类ChildActionManager也会导出ActionManager,所以GetExportedValue<T>方法会出错。

经过上面的示例,我们已经知道了,如果类是抽象的,则它的导出是不会被发现的,但,如果我们无法使得类变成抽象类,又不想它导出被发现呢?我们可以使用PartNotDiscoverable特性来描述,这样,我们改写上面的示例为下,一切会正常执行:

1
[InheritedExport]
[PartNotDiscoverable]
public class ActionManager {
    [Import]
    private Lazy<IAction> _action;

    public void DoAction() {
        _action.Value.DoAction();
    }
}

五、总结

到总结了,这次内容还是蛮多的,不过很基础,也不是很难理解,不过是导入导出而已。我们熟悉并且了解了导入导出的种类,也了解了导入导出的一些特性,假以时日,我们一定能成为导入导出的一等一高手!