C#在MEF框架中实现延迟加载部件

在MEF的宿主中,当我们通过Import声明导入的对象时,组装(Compose)的时候会创建该对象。例如:

    interface ILogger
    {
        void Log(string message);
    }

    [Export(typeof(ILogger))]
    class ConsoleLogger : ILogger
    {
        public void Log(string message)
        {
            Console.WriteLine("logger 1" + message);
        }
    }

    class Host
    {
        [Import]
        ILogger _logger = null;

        public Host()
        {
            var catalog = new AssemblyCatalog(this.GetType().Assembly);
            var container = new CompositionContainer(catalog);

            //这儿会创建ConsoleLogger对象
            container.ComposeParts(this);

            _logger.Log("hello world");
        }
    }

有的时候,有些组件的创建开销比较大,但又不会立即使用。此时,我们希望通过延迟初始化的方式将其延迟到使用的时候创建,从而提高性能(常见的是提高启动速度)。MEF是支持这一模式的,我们只需要修改一下导入的声明形式即可。

    [Import]
    Lazy<ILogger> _logger = null;

这样,Logger就会延迟到第一次使用的时候创建了。

元数据MetaData

有的时候,对于同一个服务有多个提供者,我们需要从中选择一个使用。MEF提供了ImportMany来解决这一需求。

有的时候,对于同一个服务有多个提供者,我们需要从中选择一个使用。MEF提供了ImportMany来解决这一需求。

    [Export(typeof(ILogger))]
    class ConsoleLogger : ILogger
    {
        public void Log(string message)
        {
            Console.WriteLine(message);
        }
    }

    [Export(typeof(ILogger))]
    class DbLogger : ILogger
    {
        public void Log(string message)
        {
            Console.WriteLine(message);
        }
    }

    class Host
    {
        [ImportMany]
        ILogger[] _logger = null;

        public Host()
        {
            var catalog = new AssemblyCatalog(this.GetType().Assembly);
            var container = new CompositionContainer(catalog);

            container.ComposeParts(this);

            _logger.FirstOrDefault(i => i is DbLogger).Log("hello world");
        }
    }

此时,如果我们想使用延迟导入的时候,就会变成如下形式:

    class Host
    {
        [ImportMany]
        Lazy<ILogger>[] _loggerServices = null;

        public Host()
        {
            var catalog = new AssemblyCatalog(this.GetType().Assembly);
            var container = new CompositionContainer(catalog);

            //这儿会创建ConsoleLogger对象
            container.ComposeParts(this);

            _loggerServices.FirstOrDefault(i => i.Value is DbLogger).Value.Log("hello world");
        }
    }

咋一看并没有什么问题,所有的Logger都是延迟创建的。但是仔细分析一下就会发现,要找到DbLogger的时候,必须遍历所有的_loggerServices,这个遍历会导致创建Logger。也就是说,使用第一个Logger的时候可能创建所有的Logger。

那么,如何实现我们只创建所需要的Logger呢? 这个时候就轮到元数据出场了,MEF中的元数据可以将一个数据附加到Export的服务对象中一并导出,从而可以通过元素据找到对应的服务。首先我们看看最终的效果吧:

    public interface ILoggerData
    {
        string Name { get; }
    }

    class Host
    {
        [ImportMany]
        Lazy<ILogger, ILoggerData>[] _logger = null;

        public Host()
        {
            var catalog = new AssemblyCatalog(this.GetType().Assembly);
            var container = new CompositionContainer(catalog);

            container.ComposeParts(this);

            _logger.FirstOrDefault(i => i.Metadata.Name == "DB Logger").Value.Log("hello world");
        }
    }

这里首先声明了一个元数据类型的接口ILoggerData,然后,导入的对象变成了Lazy<ILogger, ILoggerData>,这个对象有一个属性为Metadata,它的类型就是刚才声明的ILoggerData。导出的ILogger对象是延迟创建的,而元数据不是延迟创建的。我们可以通过遍历ILoggerData来找到所需要的Logger对象,从而实现只创建所使用的Logger对象。

现在的问题是:如何在导出的时候声明相关的元数据。MEF提供了两种方式:

通过ExportMetadataAttribute标记声明

这种方式是在导出的服务的时候一并通过ExportMetaDataAttribute属性标记元素据:

    [ExportMetadata("Name", "Console Logger")]
    [Export(typeof(ILogger))]
    class ConsoleLogger : ILogger
    {
        public void Log(string message)
        {
            Console.WriteLine(message);
        }
    }

    [ExportMetadata("Name", "DB Logger")]
    [Export(typeof(ILogger))]
    class DbLogger : ILogger
    {
        public void Log(string message)
        {
            Console.WriteLine(message);
        }
    }

ExportMetaDataAttribute有两个参数,Name和Value,Name为属性名称,Value为属性值。Compse的时候,MEF会先创建一个实现了元数据对象,然后将根据将Value值赋值给Name所对应的属性名称。

这么做虽然比较简单,但是它有两个弊端:

  • 1. 属性名称不是强类型

这里我们必须字符串来给标志属性名称,它们之间并没有语法级的一致性检查,在缺少nameof运算符的现在,一旦元数据属性名称更改的话是非常容易出错的。

  • 2. 如果元数据有多个值的话赋值显得非常累赘。

假如我们增加了一个Priority类型的属性,

    public interface ILoggerData
    {
        string Name { get; }
        int Priority { get; }
    }

此时,必须对所有的导出对象都增加一个ExportMetadata标记,写出如下形式:

    [ExportMetadata("Name", "DB Logger")]
    [ExportMetadata("Priority", 3)]

当属性更多一点的话相信程序员们就会骂娘了。并且一旦某个Export对象漏写了,改对象就不会被导入。这个是一个运行时的错误,非常不容易排查的。

通过Attribute标记

这种方式可以通过一个Attribute来标记元数据:

    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
    class LoggerDataAttribute : Attribute, ILoggerData
    {
        public string Name { get; private set; }

        public LoggerDataAttribute(string name)
        {
            this.Name = name;
        }
    }

    [LoggerData("Console Logger")]
    [Export(typeof(ILogger))]
    class ConsoleLogger : ILogger, ILoggerData
    {
        public string Name { get; set; }

        public void Log(string message)
        {
            Console.WriteLine(message);
        }
    }

    [LoggerData("DB Logger")]
    [Export(typeof(ILogger))]
    class DbLogger : ILogger, ILoggerData
    {
        public string Name { get; set; }
        public void Log(string message)
        {
            Console.WriteLine(message);
        }
    }

首先,声明一个LoggerDataAttribute,这个Attribute必须被MetadataAttribute标记。然后,在Export的对象前加上该LoggerDataAttribute,这样MEF导入的时候就会根据该LoggerDataAttribute创建元数据了。

值得一提的是,这里的LoggerDataAttribute本身并不需要实现ILoggerData接口,它是一个DuckType的约定,只需要实现元数据的属性即可。我这里实现该接口主要是为了让编译器保障元数据属性都有被准确实现。

到此这篇关于C#在MEF框架中实现延迟加载部件的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C# CM框架实现多页面管理的实例代码

    概述 之前我分享过一个wpf的项目实践,主页面左侧是个listbox,每次选择改变后呈现对应的页面,界面图如下 要实现这样一个功能,我之前是采用传统方式实现的,本节我采用CM框架下的Conductor<T>去实现,这样代码量可以大幅度压缩,核心代码就一行. 传统方式 后台代码:①定义集合并添加数据: public IViewModel ActiveWindowView { get; set; } public ObservableCollection<string> ListBox

  • C#在MEF框架中手动导入依赖模块

    对于简单的场景来讲,在MEF中导入依赖模块非常简单,只要用ImportAttribute标记依赖的成员,MEF模块会自动找到并创建该模块.但有的时候我们依赖的模块是上下文相关的,此时MEF框架的自动组装满足不了我们的需求了,这里以我之前的文章的一个Log插件为例: class HostModule { [Import] ILogger logger = null; public string Name { get; private set; } public HostModule(string

  • c# RPC框架的使用简介

    写在前面: RPC,听过很有段时间了,但是一直都不太清楚是干嘛的,今天我们来捋一捋. 解释: [Remote Procedure Call Protocol]远程过程调用(就是说,A程序要调用一个b方法,然而这个b方法的实现在B程序内部,B程序还可能和A不在一个电脑上面,怎么调用?http可以调用/rpc也可以,让他像调用本地方法一样调用) 使用初探: 用了一下市面上的,rpc框架,步骤如下: 1.写一个基本的代码,告诉有哪些方法. 2.然后服务端集成, 3.客户端集成, 4.OK调用生效了.

  • c# 常用框架汇总

    Json.NET http://json.codeplex.com/ Json.Net 是一个读写Json效率比较高的.Net框架.Json.Net 使得在.Net环境下使用Json更加简单.通过Linq To JSON可以快速的读写Json,通过JsonSerializer可以序列化你的.Net对象.让你轻松实现.Net中所有类型(对象,基本数据类型 等)和Json的转换. Math.NET http://www.mathdotnet.com/ Math.NET的目标是为提供一款自身包含清晰框

  • C#中使用Cache框架快速实现Cache操作

    .NET 4.0中新增了一个System.Runtime.Caching的名字空间,它提供了一系列可扩展的Cache框架,本文就简单的介绍一下如何使用它给程序添加Cache. 一个Cache框架主要包括三个部分:ObjectCache.CacheItemPolicy.ChangeMonitor. ObjectCache表示一个CachePool,它提供了Cache对象的添加.获取.更新等接口,是Cache框架的主体.它是一个抽象类,并且系统给了一个常用的实现——MemoryCache. Cach

  • C#对Xamarin框架进行数据绑定

    关于数据绑定 Xamarin 单向.双向绑定 Xaml绑定 C#代码绑定 在此之前,几段 伪代码 帮助像我一样菜的同学入门... 假如说,有两个控件,一个是滑动条(Slider),一个是显示文本的标签(Label). Slider slider = new Slider() { Maximum = 1, Value = 10 }; Label label = new Label(); label.Text = slider.Value.ToString(); 滑动条(Slider)滑动的最小单位

  • C#使用Thrift作为RPC框架入门详细教程

    前言 本文将介绍由 Facebook 开发的远程服务调用框架 Apache Thrift,它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk 等创建高效的.无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发.大数据量和多语言的环境更有优势.本文将详细介绍 Thri

  • C#在MEF框架中实现延迟加载部件

    在MEF的宿主中,当我们通过Import声明导入的对象时,组装(Compose)的时候会创建该对象.例如: interface ILogger { void Log(string message); } [Export(typeof(ILogger))] class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine("logger 1" + message); } } class

  • Yii2框架之ListView小部件的使用方法

    ListView是yii框架中类似GridView,也是用于展示多条数据的小部件,相比GridView,ListView可以更加灵活地设置数据展示的格式. 下面以我自己做的一个使用ListView来展示数据的例子,来简单讲解一下ListView小部件的使用. 首先需要在控制器中new一个数据提供器,传给视图,示例代码如下: public function actionIndex() { $dataProvider = new ActiveDataProvider([ 'query' => Dia

  • thinkPHP框架中layer.js的封装与使用方法示例

    本文实例讲述了thinkPHP框架中layer.js的封装与使用方法.分享给大家供大家参考,具体如下: v层:(还没实现功能的) <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>添加</title> </head> <body> <form action="{

  • thinkPHP5框架中widget的功能与用法详解

    本文实例讲述了thinkPHP5框架中widget的功能与用法.分享给大家供大家参考,具体如下: 注意:使用助手函数return view()渲染,则挂件功能会失败,必须使用return $this->fetch() 我们在使用模板的时候,一般网站的顶部(比如说导航栏,或者用户登录区域等等),以及网站的尾部footer(比如友情链接或者版权说明等等),和body区域,我们会为了简化代码,都会将这三个部分剖开分离,然后通过模板继承的方式来使用. 但是如果顶部和尾部只是单纯的html代码还好说,但是

  • 详解在YII2框架中使用UEditor编辑器发布文章

    本文介绍了详解在YII2框架中使用UEditor编辑器发布文章 ,分享给大家,具体如下: 创建文章数据表 文章数据表主要有4个字段 1.id  主键(int) 2.title 标题(varchar) 3.content 内容(text) 4.created_time 创建时间(int) 创建文章模型 创建文章模型,不要忘记设置验证规则和字段的名称 namespace backend\models; class Article extends \yii\db\ActiveRecord { publ

  • ABP框架中的事件总线功能介绍

    目录 事件总线 关于事件总线 为什么需要这个东西 事件总线创建过程 订阅事件 事件 发布事件 全局异常加入事件总线功能 创建事件 订阅事件 发布事件 测试 记录事件 事件总线 关于事件总线 ABP 中,为了方便进程间通讯,给开发者提供了一个叫 事件总线 的功能,事件总线分为 本地事件总线.分布式事件总线,本篇文章讲的是 本地事件总线,系列教程中暂时不考虑讲解 分布式事件总线. 事件总线 需要使用 Volo.Abp.EventBus 库,ABP 包中自带,不需要额外引入. 事件总线是通过 订阅-发

  • 全面解析JavaScript的Backbone.js框架中的Router路由

    Backbone 中的 Router 充当路由的作用,控制 URL 的走向,当在 URL 中使用 # 标签时生效. 定义 Router 至少需要一个 Router 和一个函数来映射特定的 URL,而且我们需要记住,在 Backbone 中,# 标签后的任意字符都会被 Router 接收并解释. 下面我们来定义一个 Router: <script> var AppRouter = Backbone.Router.extend({ routes: { "*actions": &

  • thinkPHP框架中执行原生SQL语句的方法

    本文实例讲述了thinkPHP框架中执行原生SQL语句的方法.分享给大家供大家参考,具体如下: 怎样在thinkphp里面执行原生的sql语句? $Model = new Model();//或者 $Model = D(); 或者 $Model = M(); $sql = "select * from `order`"; $voList = $Model->query($sql); 只是需要new一个空的模型继承Model中的方法. 注意query是查功能,execute是增删改

  • 深入解析AngularJS框架中$scope的作用与生命周期

    $scope 的使用贯穿整个 Angular App 应用,它与数据模型相关联,同时也是表达式执行的上下文.有了 $scope 就在视图和控制器之间建立了一个通道,基于作用域视图在修改数据时会立刻更新 $scope,同样的 $scope 发生改变时也会立刻重新渲染视图. 有了 $scope 这样一个桥梁,应用的业务代码可以都在 controller 中,而数据都存放在controller 的 $scope 中. $scope是一个把view(一个DOM元素)连结到controller上的对象.在

随机推荐