前言:最近去了趟外地出差,介绍推广小组开发的框架类产品。推广对象是本部门在项目上面的同事——1到2年工作经验的初级程序员。在给他们介绍框架时发现很多框架设计层面的知识他们都没有接触过,甚至没听说过,这下囧了~~于是乎在想该如何跟他们解释MEF、AOP、仓储模式等方面的东东。本来 C#基础系列 应该还有两篇关于异步的没有写完,奈何现在要推广这些个东西,博主打算先介绍下项目中目前用到的些技术,异步的往后有时间再做分享。C#进阶系列主要围绕MEF、AOP、仓储模式、Automapper、WCF等展开。本篇先来介绍下MEF的基础知识。

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在项目设计层面的用法以及带来的好处。欢迎各位拍砖斧正~~

C#进阶系列——MEF实现设计上的“松耦合”(一)的更多相关文章

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

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

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

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

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

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

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

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

  5. MEF实现设计上的“松耦合”(一)

    1.什么是MEF 先来看msdn上面的解释:MEF(Managed Extensibility Framework)是一个用于创建可扩展的轻型应用程序的库. 应用程序开发人员可利用该库发现并使用扩展, ...

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

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

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

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

  8. C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上)

    前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repository,为什么Repository会有这么大的争议,博主认为主要原 ...

  9. C#进阶系列——DDD领域驱动设计初探(四):WCF搭建

    前言:前面三篇分享了下DDD里面的两个主要特性:聚合和仓储.领域层的搭建基本完成,当然还涉及到领域事件和领域服务的部分,后面再项目搭建的过程中慢慢引入,博主的思路是先将整个架构走通,然后一步一步来添加 ...

随机推荐

  1. SharePoint 2013 版本功能对比

    前言:在SharePoint使用中,经常纠结于版本问题,SharePoint 2013主要有免费的Foundation和收费的标准版.企业版三个版本,他们之间的功能上是不一样的,找了一些资料才发现下面 ...

  2. SharePoint 2013 入门教程之入门手册

    当我们搭建完环境,创建应用程序和网站集后,就已经正式开启了我们的SharePoint之旅了,进入网站以后,开始基本的使用.设置,了解SharePoint相关特性,下面,来简单了解下SharePoint ...

  3. 基本动画CABasicAnimation - 完成之后闪回初始状态

    基本动画CABasicAnimation 结束之后,默认闪回初始状态,那怎么解决呢? position需要设备两个属性: // MARK: - 结束后不要闪回去 anim.removedOnCompl ...

  4. 【代码笔记】iOS-下拉选项cell

    一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> //加入头文件 #import "ComboBo ...

  5. 源代码管理工具之SVN

    源代码管理工具SVN是一款非常强大的源代码管理工具,现在国内70%-90%的公司都在使用SVN来管理源代码,下面就让小编给大家着重介绍一下SVN的使用,SVN的使用主要分为下面几块. SVN的使用环境 ...

  6. Android View的几个位置坐标关系

    1. View的边界,left, top, right, bottom(即左上右下),这些值都是相对View的父容器说的: 2. View的x, translationX, y, translatio ...

  7. lable计算行高

    _introduce.text=status.introduce; //设置行间距 NSMutableAttributedString *attributedString = [[NSMutableA ...

  8. Linux配置环境报“/usr/local/develop-tools/apache-maven-3.3.9/bin: 是一个目录“的解决方案

    安装Maven中 配置系统环境变量: # vi + profile M2_HOME=/usr/local/develop-tools/apache-maven- export M2_HOME PATH ...

  9. 人工智能与3A

    我在Tid2014上的一个小视频: 下一代的码农会是什么样的呢?且听咕咚老王的“3A”畅谈——“Ai.Art.Any”. 在艺术的视角下,世界是沉寂的.美丽的: 在码农的眼中,世界是有“码”的朦胧美吗 ...

  10. MySQL修改root账号密码

    MySQL数据库中如何修改root用户的密码呢?下面总结了修改root用户密码的一些方法   1: 使用set password语句修改 mysql> select user(); +----- ...