1、什么是MEF

先来看msdn上面的解释:MEF(Managed Extensibility Framework)是一个用于创建可扩展的轻型应用程序的库。 应用程序开发人员可利用该库发现并使用扩展,而无需进行配置。 扩展开发人员还可以利用该库轻松地封装代码,避免生成脆弱的硬依赖项。 通过 MEF,不仅可以在应用程序内重用扩展,还可以在应用程序之间重用扩展。

也有人把MEF解释为“依赖注入”的一种方式,那么什么是“依赖注入”?如果这样解释,感觉越陷越深......根据博主的理解,了解MEF只需要抓住以下几个关键点:

(1)字面意思,可扩展的framework,或者叫可扩展的库。也就是说,使用MEF是为了提高程序的可扩展性。MEF会根据指定的导入导出自动去发现匹配的扩展,不需要进行复杂的程序配置。

(2)在设计层面上来说,为什么要使用MEF?为了“松耦合”!我们知道,程序设计有几个原则,“高内聚,低耦合”就是其中一个。使用MEF可以帮助我们减少内库之间的耦合。

当然,如果你之前压根都没有听说过MEF,那么即使看了我上面的解释,估计也还是云里雾里。没关系,如果此刻你还有兴趣,看了下面的Demo,相信你会有一个初步的认识。

2、为什么要使用MEF:上面已经解释过,为了程序的扩展和“松耦合”。

3、MEF的使用:

(1)MEF基础导入导出的使用:

MEF的使用步骤主要分三步:宿主MEF并组合部件、标记对象的导出、对象的导入使用。

我们先来看一个Demo。

class Program2
{
     //导入对象使用
[Import("chinese_hello")]
public Person oPerson { set; get; } static void Main(string[] args)
{
var oProgram = new Program2();
oProgram.MyComposePart();
var strRes = oProgram.oPerson.SayHello("李磊");
Console.WriteLine(strRes); Console.Read();
}      //宿主MEF并组合部件
void MyComposePart()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
//将部件(part)和宿主程序添加到组合容器
container.ComposeParts(this);
}
}   public interface Person
{
string SayHello(string name);
}    //声明对象可以导出
[Export("chinese_hello", typeof(Person))]
public class Chinese : Person
{
public string SayHello(string name)
{
return "你好:" + name ;
}
} [Export("american_hello", typeof(Person))]
public class American : Person
{
public string SayHello(string name)
{
return "Hello:" + name ;
}
}

得到结果:

我们来分析下这段代码:

//宿主MEF并组合部件
void MyComposePart()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
//将部件(part)和宿主程序添加到组合容器
container.ComposeParts(this);
}

这个方法表示添加当前Program2这个类到组合容器,为什么要添加到组合容器?是因为只要添加到组合容器中之后,如果该类里面有Import,MEF才会自动去寻找对应的Export。这也就是为什么使用MEF前必须要组合部件的原因。

[Export("chinese_hello", typeof(Person))]
public class Chinese : Person
{
public string SayHello(string name)
{
return "你好:" + name ;
}
}

这里的[Export("chinese_hello", typeof(Person))]这个特性表示标记Chinese类的导出。

将Export转到定义可以看到:

//
// 摘要:
// 通过在指定协定名称下导出指定类型,初始化 System.ComponentModel.Composition.ExportAttribute 类的新实例。
//
// 参数:
// contractName:
// 用于导出使用此特性标记的类型或成员的协定名称,或 null 或空字符串 ("") 以使用默认协定名称。
//
// contractType:
// 要导出的类型。
public ExportAttribute(string contractName, Type contractType);

这里的两个参数:第一个表示协定名称,如果找到名称相同的Import,那么就对应当前的Chinese对象;第二个参数表示要导出的类型。

[Import("chinese_hello")]
public Person oPerson { set; get; }

这里的chinese_hello是和Export里面的chinese_hello对应的,由此可知,每一个[Import("chinese_hello")]这种Import一定可以找到一个对应的Export,如果找不到,程序就会报异常。当然如果这里的Import如果改成[Import("american_hello")],那么oPerson肯定就对应一个American对象。

通过上面的程序可以知道,我们使用[Import]这个特性,它的底层其实就是给我们初始化了一个对象。例如上面的[Import("chinese_hello")]等价于Person oPerson=new Chinese();。看到这里可能有人就会说这个Import是多此一举了,既然我们可以new,为什么非要用这种奇怪的语法呢,怪别扭的。其实如果我们站在架构的层面,它的好处就是可以减少dll之间的引用。

(2)MEF导入导出扩展:

按照MEF的约定,任何一个类或者是接口的实现都可以通过[System.ComponentModel.Composition.Export] 属性将其他定义组合部件(Composable Parts),在任何需要导入组合部件的地方都可以通过在特定的组合部件对象属性上使用[System.ComponentModel.Composition.Import ]实现部件的组合,两者之间通过契约(Contracts)进行通信。通过上面的例子我们可以知道,对象是可以通过Import和Export来实现导入和导出的,那么我们进一步扩展,对象的属性、字段、方法、事件等是否也可以通过[ImportAttribute]进行导入呢?

class Program2
{
[Import("TestProperty")]
public string ConsoleTest { get; set; } static void Main(string[] args)
{
var oProgram = new Program2();
oProgram.MyComposePart(); Console.WriteLine(oProgram.ConsoleTest); Console.Read();
} void MyComposePart()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
//将部件(part)和宿主程序添加到组合容器
container.ComposeParts(this);
}
} public class TestPropertyImport
{
[Export("TestProperty")]
public string TestMmport { get { return "测试属性可以导入导出"; } }
}

得到结果:

由此说明,属性也是可以导入导出的。原理与上类似。既然属性可以,那么字段就不用演示了,它和属性应该是类似的。

下面来看看方法是否可以呢?

class Program2
{
[Import("chinese_hello")]
public Person oPerson { set; get; } [Import("TestProperty")]
public string ConsoleTest { get; set; } [Import("helloname")]
public Action<string> TestFuncImport { set; get; } static void Main(string[] args)
{
var oProgram = new Program2();
oProgram.MyComposePart();
oProgram.TestFuncImport("Jim"); //Console.WriteLine(oProgram.ConsoleTest);
//var strRes = oProgram.oPerson.SayHello("李磊");
//Console.WriteLine(strRes);
Console.Read();
} void MyComposePart()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
//将部件(part)和宿主程序添加到组合容器
container.ComposeParts(this);
}
} public class TestPropertyImport
{
[Export("TestProperty")]
public string TestMmport { get { return "测试属性可以导入导出"; } } [Export("helloname", typeof(Action<string>))]
public void GetHelloName(string name)
{
Console.WriteLine("Hello:" + name);
}
}

由此可知,方法的导入和导出是通过匿名委托的方式实现的,那么由此类推,事件应该也是可以的,有兴趣的朋友可以一试。原理和上面是一样一样的。

既然属性、字段、方法、事件都可以通过Import和Export实现单一对象或变量的导入和导出,那么如果我们想要一次导入多个对象呢?嘿嘿,微软总是体贴的,它什么都为我们考虑到了。我们来看看如何实现。

class Program2
{
[ImportMany]
public IEnumerable<Person> lstPerson { set; get; } static void Main(string[] args)
{
var oProgram = new Program2();
oProgram.MyComposePart(); Console.WriteLine(oProgram.lstPerson.Count()); Console.Read();
} void MyComposePart()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
//将部件(part)和宿主程序添加到组合容器
container.ComposeParts(this);
}
} public interface Person
{
string SayHello(string name);
} [Export(typeof(Person))]
public class Chinese : Person
{
public string SayHello(string name)
{
return "你好:" + name ;
}
} [Export(typeof(Person))]
public class American : Person
{
public string SayHello(string name)
{
return "Hello:" + name ;
}
}

得到的结果为2。这里有一点需要注意的,使用ImportMany的时候对应的Export不能有chinese_hello这类string参数,否则lstPerson的Count()为0.

(3)MEF的延迟加载

我们知道,当装配一个组件的时候,当前组件里面的所有的Import的变量都自动去找到对应的Export而执行了实例化,有些时候,出于程序效率的考虑,不需要立即实例化对象,而是在使用的时候才对它进行实例化。MEF里面也有这种延迟加载的机制。

class Program2
{
[Import("chinese_hello")]
public Person oPerson { set; get; } [Import("american_hello")]
public Lazy<Person> oPerson2 { set; get; }      static void Main(string[] args)
{
var oProgram = new Program2();
oProgram.MyComposePart(); var strRes = oProgram.oPerson.SayHello("李磊");
var strRes2 = oProgram.oPerson2.Value.SayHello("Lilei");
Console.WriteLine(strRes); Console.Read();
} void MyComposePart()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
//将部件(part)和宿主程序添加到组合容器
container.ComposeParts(this);
}
}    public interface Person
{
string SayHello(string name);
} [Export("chinese_hello", typeof(Person))]
public class Chinese : Person
{
public string SayHello(string name)
{
return "你好:" + name ;
}
} [Export("american_hello", typeof(Person))]
public class American : Person
{
public string SayHello(string name)
{
return "Hello:" + name ;
}
}

通过调试可知,当程序运行到var strRes = oProgram.oPerson.SayHello("李磊");这一行的时候

oPerson对象已经实例化了,而oPerson2.Value对象没有实例化,当程序执行var strRes2 = oProgram.oPerson2.Value.SayHello("Lilei")这一句的时候,oPerson2.Value对象才进行实例化。这种需要在某些对程序性能有特殊要求的情况下面有一定的作用。

讲到这里,我们再来看前面关于理解MEF的两个关键点:

(1)可扩展的库:由于MEF允许通过Import的方式直接导入对象、属性、方法等,试想,有人开发了一个组件,他们事先定义好了一系列的导出(Export),我们只需要将它的组件引进来,使用Import的方式按照他们Export的约定导入对象即可,不用做其他复杂的配置。

(2)能更好的实现“松耦合”:比如我们项目按照面向接口编程的方式这样分层:UI层、BLL接口层、BLL实现层......UI层只需要引用BLL接口层即可,我们在BLL实现层里面定义好Export的导出规则,然后再UI层里面使用Import导入BLL实现层的对象即可,这样UI层就不需要添加BLL实现层的引用。减少了dll之间的依赖。

以上就是MEF的一些基础用法。当然在实际使用中可能不会这么简单,但是再复杂的用法都是在这些简单基础上面扩展起来的。

MEF实现设计上的“松耦合”(一)的更多相关文章

  1. C#进阶系列——MEF实现设计上的“松耦合”(二)

    前言:前篇 C#进阶系列——MEF实现设计上的“松耦合”(一) 介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能不限于此,比如MEF的目录服务.目录筛选.重组部件等高级 ...

  2. C#进阶系列——MEF实现设计上的“松耦合”(四):构造函数注入

    前言:今天十一长假的第一天,本因出去走走,奈何博主最大的乐趣是假期坐在电脑前看各处堵车,顺便写写博客,有点收获也是好的.关于MEF的知识,之前已经分享过三篇,为什么有今天这篇?是因为昨天分享领域服务的 ...

  3. MEF实现设计上的“松耦合”

    C#进阶系列——MEF实现设计上的“松耦合”(二)   前言:前篇 C#进阶系列——MEF实现设计上的“松耦合”(一) 介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能 ...

  4. C#进阶系列——MEF实现设计上的“松耦合”(一)

    前言:最近去了趟外地出差,介绍推广小组开发的框架类产品.推广对象是本部门在项目上面的同事——1到2年工作经验的初级程序员.在给他们介绍框架时发现很多框架设计层面的知识他们都没有接触过,甚至没听说过,这 ...

  5. C#进阶系列——MEF实现设计上的“松耦合”(终结篇:面向接口编程)

    序:忙碌多事的八月带着些许的倦意早已步入尾声,金秋九月承载着抗战胜利70周年的喜庆扑面而来.没来得及任何准备,似乎也不需要任何准备,因为生活不需要太多将来时.每天忙着上班.加班.白加班,忘了去愤,忘了 ...

  6. MEF实现设计上的“松耦合”(三)

    1.面向接口编程:有一定编程经验的博友应该都熟悉或者了解这种编程思想,层和层之间通过接口依赖,下层不是直接给上层提供服务,而是定义一组接口供上层调用.至于具体的业务实现,那是开发中需要做的事情,在项目 ...

  7. MEF实现设计上的“松耦合”(二)

    介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能不限于此,比如MEF的目录服务.目录筛选.重组部件等高级应用在这里就不做过多讲解,因为博主觉得这些用法只有在某些特定的环 ...

  8. Prism 4 文档 ---第9章 松耦合组件之间通信

    当构建一个大而负责的应用程序时,通用的做法时将功能拆分到离散的模块程序集中.将模块之间的静态引用最小化.这使得模块可以被独立的开发,测试,部署和升级,以及它迫使松散耦合的沟通. 当在模块之间通信时,你 ...

  9. ActiveMQ 翻译第一章 1.2小节(松耦合与ActiveMQ和何时使用ActiveMQ)

    第一章 1.2.1小节  松耦合与ActiveMQ ActiveMQ为应用程序架构提供送耦合实现组件.松耦合经常被引入到系统架构中,来减轻紧耦合的远程工程调用的使用.松耦合的设计是异步的,来自其他系统 ...

随机推荐

  1. 执行composer install后报错:执行composer install后报错: d11wtq/boris v1.0.10 requires ext-pcntl * -> the requested PHP extension pcntl is missing from your system.

    执行composer install后报错: d11wtq/boris v1.0.10 requires ext-pcntl * -> the requested PHP extension p ...

  2. linux 命令操作记录

    1.查找名为 migrations 的文件夹/文件,并删除 find . -name "migrations" -type d #查找当前目录下文件夹名为 migrations 的 ...

  3. NO.4day LINUX centos 文件基本操作

    LINUX centos 文件基本操作 1 LINUX简介 Linux的定义:Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户.多任务.支持多线程和多CP ...

  4. golang 中的 time 包的 Ticker

    真实的应用场景是:在测试收包的顺序的时候,加了个 tick 就发现丢包了 那么来看一个应用例子: package main import ( "fmt" "runtime ...

  5. 1082 线段树练习 3 && 树状数组区间修改区间查询

    1082 线段树练习 3 题意: 给定序列初值, 要求支持区间修改, 区间查询 Solution 用树状数组, 代码量小, 空间占用小 巧用增量数组, 修改时在 \(l\) 处 $ + val$ , ...

  6. java基础-基本的输入与输出

    java基础-基本的输入与输出 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.基本的输出 基本的输出,System.out 就是系统的标准输出设备,默认为显示器. 1>. ...

  7. day1 java基础语法

    一.关键字:java关键字有:abstract boolean break byte case catch char class const continue default do double el ...

  8. Intel 和AT&T 语法

    From:http://www.cnblogs.com/killerlegend/p/3906502.html Author:KillerLegend Date:2014.8.12 Intel和AT& ...

  9. 服务器能ping通ip,通不了域名解决方案

    # 将网卡配置文件配置固定ip后,添加DNS解析,然后重启网卡即可: [root@a ~]# tail -2 /etc/sysconfig/network-scripts/ifcfg-ens160 D ...

  10. Richard Stallman:让我们关注和尊敬自由软件教父

    1953年,Richard Stallman生于美国纽约曼哈顿区.在度过了并不快乐的童年之后,他在哈佛大学找到了自己的家.在MIT人工智能实验室工作期间,展露出了自己的计算 机天赋.对他来说,开发操作 ...