实战MEF(2):导出&导入
上一文中,我们大致明白了,利用MEF框架实现自动扫描并组装扩展组件的思路。本文我们继续前进,从最初的定义公共接口开始,一步步学会如何使用MEF。
在上一文中我们知道,对于每一个实现了公共规范的扩展组件,都需要进行导出,随后我们的主应用程序文件中会自动进行组装。这便产生了一个疑问:为什么需要导出?
如果大家还记得,以前我们用VC++写.dll文件时,都会把需要提供给别人调用的函数标记为导出函数,这样别人才能调用我们编写的函数。就好比我们的家,我们一般会有客厅,既然叫客厅,当然是展现给客人看的。有客人来了,我们会在客厅接待,当然我们不愿意让客人进入我们的卧室,那是较为隐私的地方。
因此,对于我们编写的扩展组件,我们要告诉MEF,哪些类应该被扫描,就像我们的网站一样,我们会过滤哪些页面允许搜索引擎进行抓取,一样的道理。
要把组件标记为可导出类型,需要在类型的定义代码上附加System.ComponentModel.Composition.ExportAttribute特性。我们可以看看ExportAttribute类的定义。
[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property|AttributeTargets.Field, AllowMultiple = true,
Inherited = false)]
public class ExportAttribute : Attribute
从定义我们看到,ExportAttribute特性可以用于类以及类的成员,能常我们会附加到整个类,以表示整个类型进行导出。
判断哪个导出类型符合组装容器导入的条件,是根据ContractName和ContractType属性。
ContractName我们可以在附加ExportAttribute时指定,也可以不指定。ContractType属性指定要导出的类型,如果不指定,默认就是当前要导出的类型。比如:
// 公共接口
public interface IMember
{
string GetMemberType();
}
[Export]
public class VipMember : IMember
{
public string GetMemberType()
{
return "VIP会员";
}
}
上面的例子,公共接口是IMember,类VipMember实现了该接口并标记为导出类型,但不指定ContractName和ContractType属性。在这种情况下,默认的协定类型为VipMember,特性附加到哪个类上,默认的导出类型就是该类的类型。
然后,我们再定义一个GenMember类。
[Export]
public class GenMember : IMember
{
public string GetMemberType()
{
return "普通会员";
}
}
这时候,对于GenMember类,导出的类型就是GenMember。
也许大家已经发现,这样定义导出类型缺点很明显,即没有一个通过的协定类型,这样一来,在组装扩展组件时就不能做到自动识别了,因为我们每扩展一个类就新一个协定类型(ContractType),这会导致主应用程序的代码需要反复修改,无法一劳永逸了。所以,通常来说,我们应当把ContractType设置为公共接口的类型,如上面例子中的IMember。故我们应该把代码改为:
[Export(typeof(IMember))]
public class VipMember : IMember
{
public string GetMemberType()
{
return "VIP会员";
}
}
[Export(typeof(IMember))]
public class GenMember : IMember
{
public string GetMemberType()
{
return "普通会员";
}
}
这样一改,就满足需求了,只要实现了IMember接口并且附加了ExportAttribute的类型都会被组装容器自动扫描,哪怕你扩展了99999999999999999999999999999个组件,它都能扫描并组装。
如果你希望组装容器在扫描类型时需要特定的类,可以在ExportAttribute中定义ContractName。这样就使得扫描类型的匹配条件变得更精准,缩小了查找范围。当然这样做也降低了智能性,因为在组装代码中,你还要去匹配协定名,这也使得主应用程序的代码会不断进行修改。
把类型导出之后,就可以提供给组装容器进行组装了。就拿我们上面的例子说吧,接下来我们对VipMember和GenMember类进行组装。
class Program
{
[Import(typeof(IMember))]
public List<IMember> AllMembers;
static void Main(string[] args)
{
// 发现类型的方式为当前程序集
AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
// 创建组装容器
CompositionContainer container = new CompositionContainer(catalog);
Program p = new Program();
// 开始组装
try
{
container.ComposeParts(p);
Console.WriteLine("---------- 测试调用 ----------");
foreach (IMember m in p.AllMembers)
{
Console.WriteLine(m.GetMemberType());
}
}
catch (CompositionException cex)
{
Console.WriteLine(cex.Message);
}
while (Console.ReadKey().Key != ConsoleKey.Escape) ;
}
}
因为我们对IMember扩展了两个类,为了能让它们全部导入,在Program类中定义了一个List<IMember>的字段,我们希望把所有导入的类型都放进这个List中。附加ImportAttribute时要与ExportAttribute相对应,前面我们只定义ContractType,所以这里导入时,我们依然使用ContractType来匹配。
这个应用程序看起来似乎没啥问题,估计可以运行了,于是我们可以按下F5看看结果。

噢,God,居然发生"奇迹"了,从异常信息中我们得知,ImportAttribute不能标记一次性导入多个类型的List的字段。那如何解决呢?莫急莫急,看看以下这个Attribute:
// 摘要:
// 指定属性、字段或参数应通过 System.ComponentModel.Composition.Hosting.CompositionContainer
// 对象用所有匹配的导出进行填充。
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public class ImportManyAttribute : Attribute, IAttributedImport
是的,这个Attribute专用于导入多个类型的,我们只要把代码改为这样就行了:
class Program
{
[ImportMany(typeof(IMember))]
public List<IMember> AllMembers;
……
再次运行,我们可以得到预期的结果了。

当今时代,我们什么东西都要绿色环保,我们的程序也不例外。上面的程序看起来是没什么大问题了。不过,如果我们要导入的类型可能会携带一些大型数据,我们要是能让它们延迟初始化那就节约了一些资源开销。虽然延迟初始化叫起来不太动听,不过也不算难以理解的概念,就好像你买体彩中了大奖,但提供方不是直接把一张张钞票拿给你,可能会先给你支票,然后,你凭支票去银行提钱。
这个延迟初始化也类似,它在声明时并不立即初始化,等到你使用的时候才初始化。System.Lazy<T>类将带领我们走向绿色环保的现代生活,它会等到你访问其Value属性时才初始化。于是,我们也把上面的代码环保一下。
class Program
{
[ImportMany(typeof(IMember))]
public List<Lazy<IMember>> AllMembers;
static void Main(string[] args)
{
……
try
{
container.ComposeParts(p);
Console.WriteLine("---------- 测试调用 ----------");
foreach (Lazy<IMember> lz in p.AllMembers)
{
Console.WriteLine(lz.Value.GetMemberType());
}
}
……
最后,提一个不重要的东西,我们代码中使用的类在
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
请引用System.ComponentModel.Composition(.dll)程序集。
本文实例的完整代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
namespace MEFExam
{
// 公共接口
public interface IMember
{
string GetMemberType();
}
[Export(typeof(IMember))]
public class VipMember : IMember
{
public string GetMemberType()
{
return "VIP会员";
}
}
[Export(typeof(IMember))]
public class GenMember : IMember
{
public string GetMemberType()
{
return "普通会员";
}
}
class Program
{
[ImportMany(typeof(IMember))]
public List<Lazy<IMember>> AllMembers;
static void Main(string[] args)
{
// 发现类型的方式为当前程序集
AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
// 创建组装容器
CompositionContainer container = new CompositionContainer(catalog);
Program p = new Program();
// 开始组装
try
{
container.ComposeParts(p);
Console.WriteLine("---------- 测试调用 ----------");
foreach (Lazy<IMember> lz in p.AllMembers)
{
Console.WriteLine(lz.Value.GetMemberType());
}
}
catch (CompositionException cex)
{
Console.WriteLine(cex.Message);
}
while (Console.ReadKey().Key != ConsoleKey.Escape) ;
}
}
}
实战MEF(2):导出&导入的更多相关文章
- 实战MEF(3):只导出类的成员
通过前面两篇文章的介绍,相信各位会明白MEF中有不少实用价值.上一文中我们也讨论了导入与导出,对于导出导入,今天我们再深入一点点,嗯,只是深入一点点而已,不会很难的,请大家务必放心,如果大家觉得看文章 ...
- C#可扩展编程之MEF学习笔记(二):MEF的导出(Export)和导入(Import)
上一篇学习完了MEF的基础知识,编写了一个简单的DEMO,接下来接着上篇的内容继续学习,如果没有看过上一篇的内容, 请阅读:http://www.cnblogs.com/yunfeifei/p/392 ...
- 实战MEF(5):导出元数据
如何理解元数 我们可以把元数据理解为随类型一起导出的附加信息.有时候我们会考虑,把元数据随类型一并导出,增加一些说明,使得我们在导入的时候,可以多一些筛选条件. 默认的类型导出带有元数据吗 上面的内容 ...
- MEF只导出类的成员
MEF只导出类的成员 通过前面两篇文章的介绍,相信各位会明白MEF中有不少实用价值.上一文中我们也讨论了导入与导出,对于导出导入,今天我们再深入一点点,嗯,只是深入一点点而已,不会很难的,请大家务必放 ...
- 导出&导入
导出&导入 上一文中,我们大致明白了,利用MEF框架实现自动扫描并组装扩展组件的思路.本文我们继续前进,从最初的定义公共接口开始,一步步学会如何使用MEF. 在上一文中我们知道,对于每一个实现 ...
- Oracle简单常用的数据泵导出导入(expdp/impdp)命令举例(上)
<Oracle简单常用的数据泵导出导入(expdp/impdp)命令举例(上)> <Oracle简单常用的数据泵导出导入(expdp/impdp)命令举例(下)> 目的:指导项 ...
- Oracle简单常用的数据泵导出导入(expdp/impdp)命令举例(下)
<Oracle简单常用的数据泵导出导入(expdp/impdp)命令举例(上)> <Oracle简单常用的数据泵导出导入(expdp/impdp)命令举例(下)> 目的:指导项 ...
- oracel数据导出导入
一.导出模式(三种模式)及命令格式 1. 全库模式 exp 用户名/密码@网络服务名 full=y file=路径\文件名.dmp log=路径\文件名.log 2. 用户模式(一般情况下采用此模式) ...
- SQL SERVER几种数据迁移/导出导入的实践
SQLServer提供了多种数据导出导入的工具和方法,在此,分享我实践的经验(只涉及数据库与Excel.数据库与文本文件.数据库与数据库之间的导出导入). (一)数据库与Excel 方法1: 使用数据 ...
- BCP导出导入大容量数据实践
前言 SQL SERVER提供多种不同的数据导出导入的工具,也可以编写SQL脚本,使用存储过程,生成所需的数据文件,甚至可以生成包含SQL语句和数据的脚本文件.各有优缺点,以适用不同的需求.下面介绍大 ...
随机推荐
- 【SAP BO】无法识别账户信息:无法访问CMS。计算机上的CMS由于某个严重错误而停止。(FWM 20031)
1.系统环境 OS:Windows Server 2008 R2 RDBMS:Oracle 11g R2(Server.Client同时存在) BI:SAP Business Objects 4.2 ...
- Canvas 最佳实践(性能篇)
Canvas 想必前端同学们都不陌生,它是 HTML5 新增的「画布」元素,允许我们使用 JavaScript 来绘制图形.目前,所有的主流浏览器都支持 Canvas. Canvas 最常见的用途是渲 ...
- [资源分享]yslow 与firebug 修复版本Firefox35【绿色版本下载】
自从火狐也开始做版本帝后,相关查的插件越来越不好使了, 而且火狐集成自己的调试工具,也不是很好使用,Yslow 也坏掉了 找公司写c++修复了下 把yslow和Firebug 打包到一起,而且关闭自动 ...
- Powershell脚本执行权限
Powershell脚本需要使用PS1扩展名 在加载脚本前需要确认是都有执行权限,默认是Restricted(受限的), 可以执行Get-ExecutionPolicy查看权限, 一般情况下使用 Re ...
- bzoj2243树链剖分+染色段数
终于做了一道不是一眼出思路的代码题(⊙o⊙) 之前没有接触过这种关于染色段数的题目(其实上课好像讲过),于是百度了一下(现在思维能力好弱) 实际上每一段有用的信息就是总共有几段和两段各是什么颜色,在开 ...
- 【填坑向】bzoj2038小Z的袜子 莫队
学莫队必做题,,,但是懒得写.今天来填个坑 莫队水题 莫队实际上就是按一个玄学顺序来离线计算询问,保证复杂度只会多一个n1/2,感觉是玄学(离线算法都很玄学) 易错点:要开long long(卡我半天 ...
- xml.etree.ElementTree对CDATA的输出
xml.etree.ElmentTree不支持CDATA 的输出,但是支持Comment的输出.由于在项目中需要输出带有CDATA块的XML文本,参考Comment的做法,修改ElmentTree中的 ...
- BZOJ2908: 又是nand
Description 首先知道A nand B=not(A and B) (运算操作限制了数位位数为K)比如2 nand 3,K=3,则2 nand 3=not (2 and 3)=not 2=5. ...
- 案例1.通过Jquery来处理复选框
实现以下功能: 1:选中第一个复选框,那么下面所有的复选框都选中,去除选中第一个复选框,下面的都不选中 2:当点击全选按钮,上面足球.篮球.游泳.唱歌 全部选中 3:当点击全不选按钮,上面四个全部取消 ...
- 关于react native
刚开始学习react native,有很多的不懂,记录一些小知识,也许下一个项目可能用到,活到老学到老........ http://www.lcode.org/react-native-viewpa ...