zoukankan      html  css  js  c++  java
  • 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();
        }
    }
    

    五、总结

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

    这次就不提供源码了,都在笔记里了。

    作者:Sun.M
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    【代码片段】HTML5嵌入媒体 (HTML5 Embedded Media)
    【代码片端】@Font-Face
    【代码片段】CSS3 渐变(CSS3 Gradients)
    【代码片段】CSS RESET,支持HTML5
    我的Cocos2d-x学习笔记(九)游戏帧循环(游戏主循环)
    我的Cocos2d-x学习笔记(八)利用CCSpriteBatchNode进行优化
    我的Cocos2d-x学习笔记(七)纹理缓存、帧缓存、精灵的创建、zOrder
    我的Cocos2d-x学习笔记(六)坐标体系与锚点
    我的Cocos2d-x学习笔记(五)CCLayer、CCLayerColor、CCLayerGradient、CCLayerMultiplex
    我的Cocos2d-x学习笔记(四)场景转换特效
  • 原文地址:https://www.cnblogs.com/prinsun/p/3002708.html
Copyright © 2011-2022 走看看