实战MEF(1)

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

文章转载于:东邪独孤

时间: 2024-12-19 09:55:22

实战MEF(1)的相关文章

实战MEF(4):搜索范围

在前面的文章中,几乎每个示例我们都会接触到扩展类的搜索位置,我们也不妨想一下,既然是自动扩展,它肯定会有一个或者多人可供查找的位置,不然MEF框架怎么知道哪里有扩展组件呢? 就像我们用导航系统去查找某个地方的所有旅店一样,正因为在该地的旅店已在数据库中注册了相关信息,我们的导航系统才能查找到它,如果某家旅店没有向导航数据库提供任何数据,那很显然导航系统是无法识别到它的详细地址的. MEF对扩展组件的查找范围通常有三个: AssemblyCatalog:从某个程序集中查找. Application

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

上一文中,我们大致明白了,利用MEF框架实现自动扫描并组装扩展组件的思路.本文我们继续前进,从最初的定义公共接口开始,一步步学会如何使用MEF. 在上一文中我们知道,对于每一个实现了公共规范的扩展组件,都需要进行导出,随后我们的主应用程序文件中会自动进行组装.这便产生了一个疑问:为什么需要导出? 如果大家还记得,以前我们用VC++写.dll文件时,都会把需要提供给别人调用的函数标记为导出函数,这样别人才能调用我们编写的函数.就好比我们的家,我们一般会有客厅,既然叫客厅,当然是展现给客人看的.有客

实战MEF(3):只导出类的成员

过前面两篇文章的介绍,相信各位会明白MEF中有不少实用价值.上一文中我们也讨论了导入与导出,对于导出导入,今天我们再深入一点点,嗯,只是深入一点点而已,不会很难的,请大家务必放心,如果大家觉得看文章枯燥,不妨一边喝牛奶一边阅读. 上一文中我们都是把整个类型(整个类)进行导出,不过有时候,我们可能会考虑只导出类的某些成员,比如某个属性或某个字段等. 我们还是少说理论,免得大家喝不下牛奶,还是直接上菜吧.为了便于测试,以下示例把组件都写在当前程序集中,也就是在同一个项目,然后用AssemblyCat

实战MEF(5):导出元数据

如何理解元数 我们可以把元数据理解为随类型一起导出的附加信息.有时候我们会考虑,把元数据随类型一并导出,增加一些说明,使得我们在导入的时候,可以多一些筛选条件. 默认的类型导出带有元数据吗 上面的内容我说得比较简洁,也许您不是很理解,不要紧,在编程里面,很多东西我们都是写了代码后才理解的.所以,我的理论功底比较差,最不擅长的就是长篇大论,还是从代码中看吧. 我们首先要弄清楚一下问题:在我没有手动去添加元数据的默认导出类型,是否带有元数据.为了使代码更简单,这里我直接把一个类导出,而不编写公共接口

【机器学习实战】Machine Learning in Action 代码 视频 项目案例

MachineLearning 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远 Machine Learning in Action (机器学习实战) | ApacheCN(apache中文网) 视频每周更新:如果你觉得有价值,请帮忙点 Star[后续组织学习活动:sklearn + tensorflow] ApacheCN - 学习机器学习群[629470233] 第一部分 分类 1.) 机器学习基础 2.) k-近邻算法 3.) 决策树 4.) 基于概率论的分类方法:朴素

下载-深入浅出Netty源码剖析、Netty实战高性能分布式RPC、NIO+Netty5各种RPC架构实战演练三部曲视频教程

下载-深入浅出Netty源码剖析.Netty实战高性能分布式RPC.NIO+Netty5各种RPC架构实战演练三部曲视频教程 第一部分:入浅出Netty源码剖析 第二部分:Netty实战高性能分布式RPC 第三部分:NIO+Netty5各种RPC架构实战演练

C#网络编程技术FastSocket实战项目演练

一.FastSocket课程介绍 .NET框架虽然微软提供了socket通信的类库,但是还有很多事情要自己处理,比如TCP协议需要处理分包.组包.粘包.维护连接列表等,UDP协议需要处理丢包.乱序,而且对于多连接并发,还要自己处理多线程等等.本期分享课程阿笨给大家带来的是来源于github开源Socket通信中间件:FastSocket,目的就是把大家从繁琐的网络编程技术中彻底地解放和释放出来. 阿笨只想安安静静的学习下网络编程技术Socket后,将学习的成果直接灵活的运用到自己的实际项目中去.

下载Java8实战视频教程

1.15套java架构师,高并发,分布式,集群,大型分布式综合项目实战详情:https://my.oschina.net/java168/blog/863547 2.36套精品Java高级课及架构课,亿级流量,P2P金融,第三方支付,设计模式实战,程序调优,系统设计:https://my.oschina.net/java168/blog/1539323 下载Java8实战视频教程

[js高手之路]设计模式系列课程-组合模式+寄生组合继承实战新闻列表

所谓组合模式,就是把一堆结构分解出来,组成在一起,现实中很多这样的例子,如: 1.肯德基套餐就是一种组合模式, 比如鸡腿堡套餐,一般是是由一个鸡腿堡,一包薯条,一杯可乐等组成的 2.组装的台式机同理,由主板,电源,内存条,显卡, 机箱,显示器,外设等组成的 把一个成型的产品组成部件,分成一个个独立的部件,这种方式可以做出很多灵活的产品,这就是组合模式的优势 比如:家用台式机电脑,要求配置比较低, 这个时候只需要主板+电源+内存条+机箱+显示器+外设就可以了,不需要配置独立显卡 鸡腿堡+鸡翅+紫薯