在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来解决这一需求。

[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的约定,只需要实现元数据的属性即可。我这里实现该接口主要是为了让编译器保障元数据属性都有被准确实现。

在MEF中实现延迟加载部件的更多相关文章

  1. 在MEF中实现延迟加载部件(转)

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

  2. MEF笔记 之延迟加载

    文章参考:在MEF中实现延迟加载部件  作者:TianFang 仅有一个服务提供者时候 using System; using System.Collections.Generic; using Sy ...

  3. 【转】MEF程序设计指南三:MEF中组合部件(Composable Parts)与契约(Contracts)的基本应用

    按照MEF的约定,任何一个类或者是接口的实现都可以通过[System.ComponentModel.Composition.ExportAttribute] 特性将其定义为组合部件(Composabl ...

  4. Mybatis中的延迟加载的使用方法

    Mybatis中的延迟加载的使用方法 在Mybatis中查询订单,然后带出商品信息和快递信息的配置方法 orderMapper.xml配置如下 <?xml version="1.0&q ...

  5. 对hibernate的延迟加载如何理解,在实际应用中,延迟加载与session关闭的矛盾是如何处理的?

    对hibernate的延迟加载如何理解,在实际应用中,延迟加载与session关闭的矛盾是如何处理的? 解答:延迟加载就是并不是在读取的时候就把数据加载进来,而是等到使用时再加载.那么Hibernat ...

  6. 在MEF中手动导入依赖的模块

    对于简单的场景来讲,在MEF中导入依赖模块非常简单,只要用ImportAttribute标记依赖的成员,MEF模块会自动找到并创建该模块.但有的时候我们依赖的模块是上下文相关的,此时MEF框架的自动组 ...

  7. Hibernate中的延迟加载及fetch

    Hibernate中的延迟加载 1.类级别的查询策略: lazy  :  true(默认值) false(立即加载) 2.多对一关联的查询策略: lazy:  proxy(默认值) no-proxy ...

  8. 在mybatis框架中,延迟加载与连表查询的差异

    1.引子 mybatis的延迟加载,主要应用于一个实体类中有复杂数据类型的属性,包括一对一和一对多的关系(在xml中用collection.association标签标识).这个种属性往往还对应着另一 ...

  9. 第三十四章、PyQt中的输入部件:QComboBox组合框功能详解

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 一.概述 Designer中输入工具部件中的Combo Box组合框与 ...

随机推荐

  1. Laravel 5.2 四、.env 文件与模型操作

    一..env文件 .env 文件是应用的环境配置文件,在配置应用参数.数据库连接.缓存处理时都会使用这个文件. // 应用相关参数 APP_ENV=local APP_DEBUG=true //应用调 ...

  2. OPENSOLARIS source

    http://blog.csdn.net/nemo2011/article/details/8543220 http://fxr.watson.org/fxr/source/?v=OPENSOLARI ...

  3. iOS一个项目开始创建, 部署到git服务器

    在做iOS开发时, 最开始可能你的经理部署项目, 所以你不会插手, 只是直接从git上clone下来然后就开始撸码, 如果有一天你做经理了, 你怎么去部署一个项目呢, 下面我来过一遍流程 1. 首先需 ...

  4. C基础 多用户分级日志库 sclog

    引言 - sclog 总的设计思路 sclog在之前已经内置到simplec 简易c开发框架中一个日志库. 最近对其重新设计了一下. 减少了对外暴露的接口. 也是C开发中一个轮子. 比较简单, 非常适 ...

  5. API(选项/数据 选项/dom)

    选项/数据 data 类型: Object | Function 限制: 组件的定义只接受function var data = { a: 1 } // 直接创建一个实例 var vm = new V ...

  6. [New learn]动画-基于UIView

    原文:https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/Ani ...

  7. K8s的内部Pod之间都不通,搞了快两天

    试了不亚于二十种方法,绝望的时候,回到了家. 想手工安装,又遇到flannel在手工下,会更改docker启动项的不完善. cni,或许就是k8s的大方向吧. 最后,抱着试一试的态度,将flannel ...

  8. AC日记——[Hnoi2017]影魔 bzoj 4826

    4826 思路: 主席树矩阵加减+单调栈预处理: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 200005 ...

  9. AC日记——旅游 bzoj 2157

    2157 思路: LCT: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 400005 #define IN ...

  10. 03 java 基础:注释 关键字 标识符 JShell

    Java 10 中已有 Jshell 工具,方便用户在其中直接输入相关 java 代码. 注释:java 中分为单行注释 //   多行注释 /*   */   文档注释 /**  */ 关键字:在 ...