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框架中实现延迟加载部件的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

时间: 2022-06-21

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# 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#中使用Cache框架快速实现Cache操作

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

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的目标是为提供一款自身包含清晰框

前端常用正则表达式汇总

前言 正则这东西吧,感觉是开发的都会接触到一些.. 那些所谓的基础语法就不说了哈,一搜一大把,来点实际的. 我这里就分享下我工作中汇总和遇到的,希望对一些小伙伴有些许帮助哈!! 正则汇总 匹配URL const regexURL = /((https?|ftp)?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/gi; // 这条正则可以匹配这么一坨格式的url //ww

正则表达式常用用法汇总

在没看正文之前,先给大家介绍下正则表达式基本概念: 正则表达式,又称正规表示法.常规表示法.(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表达式使用单个字符串来描述.匹配一系列符合某个句法规则.在很多文本编辑器里,正则表达式通常被用来检索.替换那些符合某个模式的文本. 正则表达式,有木有人像我一样,学了好几遍却还是很懵圈,学的时候老明白了,学完了忘光了.好吧,其实还是练的不够,所谓温故而知新,可以为师矣,今天就随我来复习

ASP.NET程序中常用代码汇总

1. 打开新的窗口并传送参数: //传送参数: response.write("<script>window.open('*.aspx?id="+this.DropDownList1.SelectIndex+"&id1="++"')</script>") //接收参数: string a = Request.QueryString("id"); string b = Request.QueryS

javascript常用正则表达式汇总

javascript常用正则表达式汇总 /** * 检验各种规则 * @param str 检验的内容 * @param cType 预设的检验规则 字符串[ * empty, 检验是否为空 * telphone, 座机手机号码 * allphone, 所有手机号码 * ydphone, 移动手机号码 * ltphone, 联通手机号码 * dxphone, 电信手机号码 * email, 邮箱 * url, 网址 * cn, 汉字 * image, 图片格式 * emscode, 邮政编码 *

PHP常用技巧汇总

本文为大家分享了多个php常用技巧,供大家参考,具体内容如下 1.PHP文件读取函式 //文件读取函式 function PHP_Read($file_name) { $fd=fopen($file_name,r); while($bufline=fgets($fd, 4096)){ $buf.=$bufline; } fclose($fd); return $buf; } ?> 2.文件写入函式 //文件写入函式 function PHP_Write($file_name,$data,$met

Mysql常用命令汇总

一.Mysql安装目录 数据库目录:/var/lib/mysql/ 配置文件:/usr/share/mysql(mysql.server命令及配置文件) 相关命令:/usr/bin(mysqladmin mysqldump等命令) 启动脚本:/etc/init.d/mysql(启动脚本文件mysql的目录) 二.系统管理 连接MySQL 格式: mysql -h 主机地址 -u用户名 -p用户密码 例 1:连接到本机上的MySQL. 复制代码 代码如下: hadoop@ubuntu:~$ mys

Java常用命令汇总

这篇文章就主要向大家展示了Java编程中常用的命令,下面看下具体内容. 1.javac 将文件编译成.class文件 用法: javac <options> <source files> 其中, 可能的选项包括: -g 生成所有调试信息 -g:none 不生成任何调试信息 -g:{lines,vars,source} 只生成某些调试信息 -nowarn 不生成任何警告 -verbose 输出有关编译器正在执行的操作的消息 -deprecation 输出使用已过时的 API 的源位置

C# 10个常用特性汇总

1) async / await 使用 async / await 模式,可以在执行代码块操作的时候不会阻塞 UI 或者当前的线程.即使该操作被某些执行动作延迟了(比如一个 web 请求),async / await 模式也会继续执行后续的代码. 微软文档:https://msdn.microsoft.com/zh-cn/library/hh191443.aspx 2) 对象 / 数组 / 集合的初始值设定项(initializers) 通过使用对象.数组.集合的初始值设定项,可以很容易地创建类

Eclipse中常用快捷键汇总

Eclipse是一个很强大的.免费的开发平台,Java常用此工具来做开发.快捷键作为提升开发效率的必备手段,有必要了解一下,下面介绍了常用的快捷键. Eclipse中的快捷键 1.补全代码的声明:alt + / 2.快速修复: ctrl + 1 3.使用单行注释:ctrl + / 4.使用多行注释: ctrl + shift + / 5.取消多行注释:ctrl + shift + \ 6.复制指定行的代码:ctrl + alt + down 或 ctrl + alt + up 7.删除指定行的代

服务器Ubuntu常用命令汇总

下面的命令大都需要在 控制台 / 终端 / shell 下输入. 任何一个使用 'sudo' 作为前缀的命令都需要拥有管理员 (或 root) 访问权限. 所以你会被提示输入你自己的密码. 查看软件xxx安装内容 查看显卡使用情况 nvidia-smi 查看硬盘使用情况 df -hl # 查看磁盘剩余空间 df -h # 查看每个根路径的分区大小 查看用户配额及使用情况 quota -uvs username 打开visdom python -m visdom.server 后面可加参数,如在9