在过去,我们完成一套应用程序后,如果后面对其功能进行了扩展或修整,往往需要重新编译代码生成新的应用程序,然后再覆盖原来的程序。这样的扩展方式对于较小的或者不经常扩展和更新的应用程序来说是可以接受的,而对于像ERP系统那样复杂而且常常需要扩展的应用程序,这种扩展方法就不够方便,因为每次都要修改源代码或重新引用组件。

尤其是组件(许多dll),如果每编写一个新组件又要在主项目中引用一次,显然主项目就不得不经常重新生成。要是能有一种机制,可以在主项目应用程序不作任何修改就可以自动识别并扩展组件,就会很便捷,我们每次扩展只需要更新或者添加某些dll文件即可。

MEF正是为了解决上述问题而诞生。MEF全称Managed Extensibility Framework,至于如何翻译不重要,你喜欢怎么个译法都无所谓,我们只要明白它用来干啥就好了。

宽泛的理论似乎作用不明显,我们还是先来弄一个简单的例子。现在假设我在开发一个应用程序,首先我要为一些组件以及将来可以要扩展的组件定义公共接口(或者说是协定,大家是否记得在WCF中也是这样,先定义一些公共的服务协定,然后视具体情况对这些协定进行扩展),然后我可以按照不同的情形去实现这些接口,这也是我们常说的,接口可以起到规范作用,有了规范,正是为后期扩展打下可行性基础。

例子的主项目是一个控制台应用程序,我们先在解决方案中添加一个类库项目,为了简单演示,我定义了以下接口:

public interface IExtBase
 
 {
 
void DoTask();
 
string TaskName { get; }
 
 }

这个IExtBase接口就作为我们要扩展的组件的公共协定,不管我以后怎么扩展,哪怕我要添加100000个组件,这些组件都要实现IExtBase接口。

这里我做了两个扩展作为例子,为了表明MEF框架能自动发现组件,我把两个实现IExtBase接口的类写到另外一个类库项目中——TaskToa.dll。

[Export("task1", typeof(CommExtBase.IExtBase))]
 
public class Task_1 : CommExtBase.IExtBase
 
 {
 
public void DoTask()
 
 {
 
Console.WriteLine("任务1执行。");
 
 }
 
  
 
public string TaskName
 
 {
 
get
 
 {
 
return "任务1";
 
 }
 
 }
 
 }
 
  
 
 [Export("task2", typeof(CommExtBase.IExtBase))]
 
public class Task_2 : CommExtBase.IExtBase
 
 {
 
public void DoTask()
 
 {
 
Console.WriteLine("任务2执行。");
 
 }
 
  
 
public string TaskName
 
 {
 
get { return "任务2"; }
 
 }
 
 }

附加ExportAttribute特性用于扩展的组件类,表示它们将被导出,导出的类型会被MEF自动发现。

在主项目中我们不引用这个TaskToa类库,先把TaskToa项目生成一个TaskToa.dll,直接复制到.exe应用程序的动行目录下,在调试模中为\\项目\\bin\\Debug目录下。

由于实现公共接口的类不止一个,后续可能还有10000000个,为了能够使所有的扩展组件都能被发现,统一的协定类型为IExtBase接口(与WCF的实现服务协定相似),在附加ExportAttribute特性时指定了每个组件类的协定名,而协定类型都是IExtBase接口,协定类型必须统一才能保证所有扩展的类能被MEF框架发现。

最后在.exe主项目的代码中加入以下代码:

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; 

namespace MainApp 

{ 

class TestWork

 { 

 [Import("task1")] 

public CommExtBase.IExtBase Task1; 

 [Import("task2")] 

public CommExtBase.IExtBase Task2; 

 } 

class Program

 { 

static void Main(string[] args) 

 { 

ApplicationCatalog appCat = new ApplicationCatalog(); 

CompositionContainer container = new CompositionContainer(appCat); 

TestWork tw = new TestWork(); 

try

 { 

 container.ComposeParts(tw); 

Console.WriteLine("Task1的类型:{0}\tTaskName: {1}\t调用DoTask方法:",tw.Task1.GetType().Name,tw.Task1.TaskName); 

 tw.Task1.DoTask(); 

Console.Write("\n\n"); 

Console.WriteLine("Task2的类型:{0}\tTaskName:{1}\t调用DoTask方法:", 

tw.Task2.GetType().Name, tw.Task2.TaskName); 

 tw.Task2.DoTask(); 

 } 

catch (CompositionException cex) 

 { 

Console.WriteLine(cex.Message); 

 } 

Console.Read(); 

 } 

 } 

} 

TestWork类用来包装最后被合并的组件,它有两个公共字段,类型虽然都是IExtBase,但由于应用了ImportAttribute特性,并且指定了协定名,这些协定名一定要与我们之前在扩展类中应用ExportAttribute是指定的协定名相对应。附加了ImportAttribute特性可以让MEF识别对应的组件并导入到TestWork类中。

在Main入口点中,我们先使用ApplicationCatalog类来收集所有可用的扩展组件,然后把收集到的信息传给CompositionContainer容器,容器负责把收集到的组件进行合并(组装)。合并完成后我们就可以使用这些组件了。

本例子的运行结果如下面的截图所示:

从截图中我们看到,TestWork类的Task1和Task2字段的类型分别为Task_1和Task_2,同时也调用了它们的成员,输出结果表明,我们之前开发的两个扩展类Task_1和Task_2已经自动导入到我们当前的应用程序中了。

ApplicationCatalog类是在当前应用程序的运行目录下查找所有符合要求的exe或dll中的扩展组件,一旦找到就自动收集并生成组件目录,而后提供给CompositionContainer进行组装。

从这个例子我们看到MEF框架就像一个大型的组装厂车间,首先设计师们寻找灵感,构思产品的基本模型,这也就是我们所定义的公共接口规范;随后,进行精确计算,进一步把抽象的模型变为具体的工程图,这相当于我们自己实现编写的各个扩展类;接着,相关工作人员会把设计师和工程师做好的各个零部件的工程图收集整理,准备提供给车间进行生产组装,这就相当于我们例子中的ComposablePartCatalog,我们例子中用到的ApplicationCatalog只是其中一个收集方式,其他的方式还有按程序集进行收集或按特定路径目录下的所有类库进行收集。然后车间开始制作并组装成产品,最终投入使用。

我们可以用下面的图来描述一下整个过程(此图纯属虚构,如有雷同,实属巧合)

现在我们先不必过多关注代码细节,因为后面我会慢慢介绍,我们只要明白MEF的用途就可以了。

文章转载于:东邪独孤

实战MEF(1)一种不错的扩展方式的更多相关文章

  1. 实战MEF(1):一种不错的扩展方式

    在过去,我们完成一套应用程序后,如果后面对其功能进行了扩展或修整,往往需要重新编译代码生成新的应用程序,然后再覆盖原来的程序.这样的扩展方式对于较小的或者不经常扩展和更新的应用程序来说是可以接受的,而 ...

  2. Jenkins持续集成企业实战系列之两种网站部署的流程-----01

    注:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.    最初接触Jenkins也是由于公司需求,根据公司需求Java代码项目升级的.(公司是 ...

  3. 两种不同的扩展Scrum的方式

    两种不同的扩展Scrum的方式 1.LeSS和LeSS Huge –大型Scrum LeSS(和LeSS Huge –真正的大型程序)的合著者Craig Larman首先批评了管理,开发人员和客户传统 ...

  4. 实战MEF(4):搜索范围

    在前面的文章中,几乎每个示例我们都会接触到扩展类的搜索位置,我们也不妨想一下,既然是自动扩展,它肯定会有一个或者多人可供查找的位置,不然MEF框架怎么知道哪里有扩展组件呢? 就像我们用导航系统去查找某 ...

  5. 实战MEF(2):导出&导入

    上一文中,我们大致明白了,利用MEF框架实现自动扫描并组装扩展组件的思路.本文我们继续前进,从最初的定义公共接口开始,一步步学会如何使用MEF. 在上一文中我们知道,对于每一个实现了公共规范的扩展组件 ...

  6. jQuery插件主要有两种扩展方式

    jQuery插件主要有两种扩展方式: 扩展全局函数方式. 扩展对象方法方式. 扩展全局函数方式 扩展全局函数方式定义的插件,即类级别插件,可以通过jQuery.extend()来进行定义.定义格式为: ...

  7. Yii实战中8个必备常用的扩展,模块和widget

    Yii实战中8个必备常用的扩展,模块和widget 在经过畅K网 的实战后,总结一下在Yii的项目中会经常用到的组件和一些基本的使用方法,分享给大家,同时也给自己留个备忘录,下面我以代码加图片说明. ...

  8. 三种Tomcat集群方式的优缺点分析

    三种Tomcat集群方式的优缺点分析 2009-09-01 10:00 kit_lo kit_lo的博客 字号:T | T 本文对三种Tomcat集群方式的优缺点进行了分析.三种集群方式分别是:使用D ...

  9. http 3种web会话管理方式

    http是无状态的,一次请求结束,连接断开,下次服务器再收到请求,它就不知道这个请求是哪个用户发过来的.当然它知道是哪个客户端地址发过来的,但是对于我们的应用来说,我们是靠用户来管理,而不是靠客户端. ...

随机推荐

  1. increment/decrement/dereference

    #include <vector> #include <deque> #include <algorithm> #include <iostream> ...

  2. 为什么 “return s and s.strip()” 在用 filter 去掉空白字符时好使?

    如题: 给定一个数组,其中该数组中的每个元素都为字符串,删除该数组中的空白字符串. _list = ["A", "", "", " ...

  3. etcd启用https服务

    目录 cfssl相关工具下载 生成etcd所需要的ssl证书 生成ca证书 生成etcd服务端证书 生成etcd客户端证书 修改etcd集群配置文件 重启etcd集群 验证集群健康情况 关于etcd的 ...

  4. GO_00:Mac之Item2的配置安装

    申明:Mac 本上的终端感觉不是那么多好用,以前在 Windows 上操作 Linux 都是通过 XShell 来操作的,界面美观大方.操作使用比较简单.故在 Mac 上也需要有这样一款类似的工具,那 ...

  5. P2054 [AHOI2005]洗牌

    P2054 [AHOI2005]洗牌 题目描述 为了表彰小联为Samuel星球的探险所做出的贡献,小联被邀请参加Samuel星球近距离载人探险活动. 由于Samuel星球相当遥远,科学家们要在飞船中度 ...

  6. enumerate 遍历numpy数组

    enumerate 遍历numpy数组 觉得有用的话,欢迎一起讨论相互学习~Follow Me 遍历一维数组 i,j 分别表示数组的 索引 和 存储的值 import numpy as np a=np ...

  7. [译]Quartz.NET 框架 教程(中文版)2.2.x 之第五课 SimpleTrigger

    第五课 SimpleTrigger 如果你需要在一个指定时间段内执行一次作业任务或是在指定的时间间隔内多次执行作业任务,SimpleTrigger应该能满足你的调度需求.例如,你希望触发器在2015年 ...

  8. codeforces9D How many trees?

    传送门:http://codeforces.com/problemset/problem/9/D [题解] 树形dp,f(i,j)表示i个节点,高度为j的方案数,枚举左子树大小和哪一个子树高度为j-1 ...

  9. 20155231 2016-2017-2 《Java程序设计》第8周学习总结

    20155231 2016-2017-2 <Java程序设计>第8周学习总结 教材学习内容总结 学习目标 了解NIO 会使用Channel.Buffer与NIO2 会使用日志API.国际化 ...

  10. Linux基础-网络配置

    任务目标:临时配置网络ip,网关,DNS,然后重启network:写配置文件永久保存网络配置 临时配置ens33网卡IP地址为192.168.30.99,查看更改完的ifconfig信息: 重新启动n ...