又到了写笔记的时候了,这次的内容网罗了MEF中的所有Attribute,感觉内容偏多,所以分为两个篇幅来记录,篇幅内容过多的话,感觉不太适合阅读。
一、基本导入导出
基本导入和导出在前两篇博客中都有涉及到,这里再简单的补充一下。
最基本的导入导出
1 2 3
| public interface IAction { void DoAction(); }
|
上面这个接口是一个契约(Contract),会一直贯穿本篇文章的示例代码中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| [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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| [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 2 3 4 5 6 7 8 9 10 11 12
| [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 2
| [Export(typeof(IAction))] private static IAction MyExportAction { get; set; }
|
导入导出方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| [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 2 3 4 5 6 7 8 9 10
| 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| 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);
var actionManager = _container.GetExportedValue<ActionManager>();
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 2 3 4 5
| [ImportMany(typeof(IAction))] private IEnumerable<IAction> _actions;
[ImportMany(typeof(IAction))] private Lazy<IAction>[] _lazyActions;
|
值得注意的是,我们在导入构造函数时,即ImportingConstructor,如果构造函数的参数是IEumerable类型,使用ImportingConstructor导入的话,回去寻找IEumerable类型的导出,而不是T类型的一组导出,如果我们需要的是T类型的一组导出的话,可以使用下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| [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 2
| [Import(typeof(IAction), AllowDefault = true)] public IAction _myAction;
|
使用AllowDefault
为true
的情况下,该导入便是可选导入,如果匹配不上,上诉示例的字段值为空(null)。
三、导入和导出的继承
在MEF中,Export
特性是不会被继承的,如果希望子类继承父类的导出,则要使用InheritedExport
特性;而Import
特性是继承的,也就是说,如果父类中引入了某个导入,则子类中依然会引用该导入。我们看示例:
1 2 3 4 5 6 7 8 9 10 11
| [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 2 3 4 5 6 7 8 9 10
| [InheritedExport] [PartNotDiscoverable] public class ActionManager { [Import] private Lazy<IAction> _action;
public void DoAction() { _action.Value.DoAction(); } }
|
五、总结
到总结了,这次内容还是蛮多的,不过很基础,也不是很难理解,不过是导入导出而已。我们熟悉并且了解了导入导出的种类,也了解了导入导出的一些特性,假以时日,我们一定能成为导入导出的一等一高手!