MEF核心笔记(3)细说MEF中的Attribute [上]
2013-04-06 17:25 Sun.M 阅读(3116) 评论(4) 编辑 收藏 举报又到了写笔记的时候了,这次的内容网罗了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(); } }
五、总结
到总结了,这次内容还是蛮多的,不过很基础,也不是很难理解,不过是导入导出而已。我们熟悉并且了解了导入导出的种类,也了解了导入导出的一些特性,假以时日,我们一定能成为导入导出的一等一高手!
这次就不提供源码了,都在笔记里了。