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

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

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

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

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

  1. public interface IExtBase
  2.  
  3. {
  4.  
  5. void DoTask();
  6.  
  7. string TaskName { get; }
  8.  
  9. }

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

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

  1. [Export("task1", typeof(CommExtBase.IExtBase))]
  2.  
  3. public class Task_1 : CommExtBase.IExtBase
  4.  
  5. {
  6.  
  7. public void DoTask()
  8.  
  9. {
  10.  
  11. Console.WriteLine("任务1执行。");
  12.  
  13. }
  14.  
  15. public string TaskName
  16.  
  17. {
  18.  
  19. get
  20.  
  21. {
  22.  
  23. return "任务1";
  24.  
  25. }
  26.  
  27. }
  28.  
  29. }
  30.  
  31. [Export("task2", typeof(CommExtBase.IExtBase))]
  32.  
  33. public class Task_2 : CommExtBase.IExtBase
  34.  
  35. {
  36.  
  37. public void DoTask()
  38.  
  39. {
  40.  
  41. Console.WriteLine("任务2执行。");
  42.  
  43. }
  44.  
  45. public string TaskName
  46.  
  47. {
  48.  
  49. get { return "任务2"; }
  50.  
  51. }
  52.  
  53. }

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

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

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

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

  1. using System;
  2.  
  3. using System.Collections.Generic;
  4.  
  5. using System.Linq;
  6.  
  7. using System.Text;
  8.  
  9. using System.Threading.Tasks;
  10.  
  11. using System.ComponentModel.Composition;
  12.  
  13. using System.ComponentModel.Composition.Hosting;
  14.  
  15. namespace MainApp
  16.  
  17. {
  18.  
  19. class TestWork
  20.  
  21. {
  22.  
  23. [Import("task1")]
  24.  
  25. public CommExtBase.IExtBase Task1;
  26.  
  27. [Import("task2")]
  28.  
  29. public CommExtBase.IExtBase Task2;
  30.  
  31. }
  32.  
  33. class Program
  34.  
  35. {
  36.  
  37. static void Main(string[] args)
  38.  
  39. {
  40.  
  41. ApplicationCatalog appCat = new ApplicationCatalog();
  42.  
  43. CompositionContainer container = new CompositionContainer(appCat);
  44.  
  45. TestWork tw = new TestWork();
  46.  
  47. try
  48.  
  49. {
  50.  
  51. container.ComposeParts(tw);
  52.  
  53. Console.WriteLine("Task1的类型:{0}\tTaskName: {1}\t调用DoTask方法:",tw.Task1.GetType().Name,tw.Task1.TaskName);
  54.  
  55. tw.Task1.DoTask();
  56.  
  57. Console.Write("\n\n");
  58.  
  59. Console.WriteLine("Task2的类型:{0}\tTaskName:{1}\t调用DoTask方法:",
  60.  
  61. tw.Task2.GetType().Name, tw.Task2.TaskName);
  62.  
  63. tw.Task2.DoTask();
  64.  
  65. }
  66.  
  67. catch (CompositionException cex)
  68.  
  69. {
  70.  
  71. Console.WriteLine(cex.Message);
  72.  
  73. }
  74.  
  75. Console.Read();
  76.  
  77. }
  78.  
  79. }
  80.  
  81. }

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的用途就可以了。

OK,本文就说到这里吧,88

实战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. Mysql在windows系统下的配置

    因为项目测试需求,不得不在本地装一个Mysql才能更方便地进行程序调试,整个过程虽然简单,但也遇到了一点麻烦,所以贴出来当是备忘. 这里采用MySQL Community Server  5.7.12 ...

  2. XVI Open Cup named after E.V. Pankratiev. GP of Ukraine

    A. Associated Vertices 首先求出SCC然后缩点,第一次求出每个点能到的点集,第二次收集这些点集即可,用bitset加速,时间复杂度$O(\frac{nm}{64})$. #inc ...

  3. linux菜鸟日记

    本地yum源的安装: 要安装本地yum源,首先需要熟悉本地yum文件的配置和光盘的挂载 第一步挂载光盘: 首先需要指定一个光盘挂载目录 通常情况下我习惯使用默认挂载目录,所以一般我使用的光盘挂载命令是 ...

  4. 如何使CEF支持Flash

    方法一:复制Chrome浏览器下的pepperFlash,通过cef命令行参数设置路径. public Form1() { InitializeComponent(); InitializeChrom ...

  5. HDU 2087  KMP模板题

    1.HDU 2087 2.题意:一个主串,一个子串,求子串在主串里出现了几次. 3.总结:看了题解,还是不太懂.. //#include<iostream>#include<cmat ...

  6. bootstrap之排版类

    bootstrap之排版类

  7. VS调式显示问题

    调式时,发现与以前的显示不太一样,虽然也能看到结果,但不是很方便,后来网上查找到与VS中的一个文件被修改有关. 找个别人安装过的VS2005,替换Common7\Packages\Debugger\a ...

  8. CentOS安装配置redis

    安装前准备,安装gcc 先用 gcc -v命令检测本机是否安装gcc,如果没有则用下面命令安装: yum install cpp yum install binutils yum install gl ...

  9. Smart3D系列教程7之 《手动配置S3C索引加载全部的瓦片数据》

    一.前言 迄今为止,Wish3D已经出品推出了6篇系列教程,从倾斜摄影的原理方法.采集照片的技巧.Smart3D各模块的功能应用.小物件的照片重建.大区域的地形重建到DSM及正射影像的处理生产,立足于 ...

  10. *HDU 1757 矩阵乘法

    A Simple Math Problem Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Ot ...