之前公司里用到了一个叫MEF的东西,说来惭愧一直只管写代码却不曾理解MEF框架为何物,今天就来学习一下,这是一篇迟到了不知多久的博客。

--------------------------------------------------------进入正题-------------------------------------------------------------------

1.MEF概念

MEF,全称Managed Extensibility Framework(托管可扩展框架)。单从名字我们不难发现:MEF是专门致力于解决扩展性问题的框架,MSDN中对MEF有这样一段说明:

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

2.MEF使用

  MEF的使用范围广泛,在Winform、WPF、Win32、Silverlight中都可以使用,我们就从控制台说起,看看控制台下如何实现MEF,下面先新建一个win32控制台项目MEF_1的Demo,添加一个IBookService接口一个继承这个接口的类MusicBook ,如图:

首先需要手动添加对System.ComponentModel.Composition命名空间的引用,由于在控制台程序中没有引用这个DLL,所以要手动添加:

我们再回到program类中写如下代码:

  1. class Program
  2. {
  3. [Import]
  4. public IBookService Service { get; set; }
  5.  
  6. static void Main(string[] args)
  7. {
  8. Program pro = new Program();
  9. pro.Compose();
  10. if (pro.Service != null)
  11. {
  12. Console.WriteLine(pro.Service.GetBookName());
  13. }
  14. Console.Read();
  15. }
  1. //宿主MEF并组合部件
  1. private void Compose() {
    //获取包含当前执行的代码的程序集
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  2. CompositionContainer container = new CompositionContainer(catalog);
  1. //将部件(part)和宿主程序添加到组合容器
  1. container.ComposeParts(this); }
    }

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

代码运行情况:

可以看到调用了MusicBook类的GetBookName方法,但是我们并没有实例化MusicBook类,是不是很神奇呢???

这一就是实现了主程序和类之间的解耦,大大提高了代码的扩展性和易维护性!

看到这里可能有人就会说这个Import是多此一举了,既然我们可以new,为什么非要用这种奇怪的语法呢,怪别扭的。其实如果我们站在架构的层面,它的好处就是可以减少dll之间的引用。

------------------------------------------------MEF中的Import(导入)和Export(导出)-------------------------------------

上面的测试小Demo我们对MEF有了一个初步的了解,下面我们来细看一下MEF中的Import(导入)和Export(导出)。

  1. class Program
  2. {
  3. [Import("MusicBook")]
  4. public IBookService Service { get; set; }
  5.  
  6. static void Main(string[] args)
  7. {
  8. Program pro = new Program();
  9. pro.Compose();
  10. if (pro.Service != null)
  11. {
  12. Console.WriteLine(pro.Service.GetBookName());
  13. }
  14. Console.Read();
  15. }
  16. //宿主MEF并组合部件
  17. private void Compose()
  18. {
  19. //获取包含当前执行的代码的程序集
  20. var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  21. CompositionContainer container = new CompositionContainer(catalog);
  22. //将部件(part)和宿主程序添加到组合容器
  23. container.ComposeParts(this);
  24. }
  25. }
  26. }
  1. ////Export(typeof(IBookService)) 这句话将类声明导出为IBookService接口类型
  2. //[Export(typeof(IBookService))]
  3. [Export("MusicBook",typeof(IBookService))]
  4. class MusicBook:IBookService
  5. {
  6.  
  7. public string BookName { get; set; }
  8.  
  9. public string GetBookName()
  10. {
  11. return "音乐书本";
  12. }
  13. }

注意,标红的是改动过的地方,其他地方的代码没有变,上一次我们使用的是Export的方法是[Export(typeof(IBookService))],这次前面多了一个参数,没错,这个就是一个契约名,名字可以随便起,而且可以重复,但是如果名字乱起,和其他DLL中的重复,到时候会导致程序出现很多Bug,最好按照一定的规范去起名字。

这里有了契约名以后,导入(Import)时就要指定的契约名,否则将无法找到MusicBook,Export还有一个方法是[Export("Name")],这个方法只指定了契约名,没有指定导出类型,那么默认的导出类型是object类型,在导入时导出到的对象就要为object类型,否则将匹配不到那个组件。

  到现在,我们只写了一个接口和一个实现类,导出的也是一个类,下面我们多添加几个类来看看会怎么样,为了方便大家测试,我把实现接口的类写在一个文件里面,新加几个类后,的MusicBook类文件代码如下:

  1. [Export("MusicBook",typeof(IBookService))]
  2. public class MusicBook:IBookService
  3. {
  4.  
  5. public string BookName { get; set; }
  6.  
  7. public string GetBookName()
  8. {
  9. return "音乐书本";
  10. }
  11. }
  12. [Export("MusicBook", typeof(IBookService))]
  13. public class MathBook : IBookService
  14. {
  15.  
  16. public string BookName { get; set; }
  17.  
  18. public string GetBookName()
  19. {
  20. return "数学课本";
  21. }
  22. }
  23. [Export("MusicBook", typeof(IBookService))]
  24. public class HistoryBook:IBookService
  25. {
  26.  
  27. public string BookName { get; set; }
  28.  
  29. public string GetBookName()
  30. {
  31. return "历史课本";
  32. }
  33. }

这里添加两个类,HistoryBook和MathBook,都继承自IBookService接口,注意他们的契约名都相同,都为MusicBook,后面再详细的说这个问题,修改后的program的代码如下:

  1. class Program
  2. {
  3. [ImportMany("MusicBook")]
  4. public IEnumerable<IBookService> Services { get; set; }
  5.  
  6. static void Main(string[] args)
  7. {
  8. Program pro = new Program();
  9. pro.Compose();
  10. if (pro.Services != null)
  11. {
  12. foreach (var service in pro.Services)
  13. {
  14. Console.WriteLine(service.GetBookName());
  15. }
  16. }
  17. Console.Read();
  18. }
  19. //宿主MEF并组合部件
  20. private void Compose()
  21. {
  22. //获取包含当前执行的代码的程序集
  23. var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  24. CompositionContainer container = new CompositionContainer(catalog);
  25. //将部件(part)和宿主程序添加到组合容器
  26. container.ComposeParts(this);
  27. }
  28. }

这里需要注意的是标红的两行代码,[ImportMany("MusicBook")]还有下面的声明变成了IEnumerable<>,因为要导出多个实例,所以要用到集合,下面采用foreach遍历输出,运行的结果如下图:

现在三个类的契约名都不相同了,其他的代码不动,再次运行程序看看,是不是现在只输出MusicBook了,同理,修改[Import("Name")]中的契约名称,就会导入指定含有名称的类,契约名可以重复,这一以来,我们就可以用契约名给类进行分类,导入时可以根据契约名来导入。

注意:IEnumerable<T>中的类型必须和类的导出类型匹配,如类上面标注的是[Exprot(typeof(object))],那么就必须声明为IEnumerable<object>才能匹配到导出的类。

例如:我们在类上面标注[Export("Book")],我们仅仅指定了契约名,而没有指定类型,那么默认为object,此时还用IEnumerable<IBookService>就匹配不到。

那么,这种情况就要在输出是进行强制类型转换,代码如下:

  1. [Export("MusicBook")]
  2. public class MusicBook:IBookService
  3. {
  4.  
  5. public string BookName { get; set; }
  6.  
  7. public string GetBookName()
  8. {
  9. return "音乐书本";
  10. }
  11. }

如果我们这样写了那样结果就只能匹配到后两个:

此时我们需要强制转换一下输出的类型:

  1. [ImportMany("MusicBook")]
  2. public IEnumerable<object> Services { get; set; }
  3.  
  4. static void Main(string[] args)
  5. {
  6. Program pro = new Program();
  7. pro.Compose();
  8. if (pro.Services != null)
  9. {
  10. foreach (var service in pro.Services)
  11. {
  12. var ss = (IBookService)service;
  13. Console.WriteLine(ss.GetBookName());
  14. }
  15. }
  16. Console.Read();
  17. }

结果就匹配到了所有类的输出:

------------------------------------------------MEF中方法和属性能不能导出呢?---------------------------------------------

前面说完了导入和导出的几种方法,如果大家细心的话会注意到前面我们导出的都是类,那么方法和属性能不能导出呢???答案是肯定的,下面就来说下MEF是如何导出方法和属性的:

首先是导出属性:

  1. [Export("MusicBook", typeof(IBookService))]
  2. public class MathBook : IBookService
  3. {
  4. //导出私有的属性
  5. [Export(typeof(string))]
  6. private string _privateBookName = "私有的属性BookName";
  7. //导出公用的属性
  8. [Export(typeof(string))]
  9. public string _publicBookName = "公有的属性BookName";
  10.  
  11. public string BookName { get; set; }
  12.  
  13. public string GetBookName()
  14. {
  15. return "数学课本";
  16. }
  17. }
  1. class Program
  2. {
  3. [ImportMany("MusicBook")]
  4. public IEnumerable<object> Services { get; set; }
  5.  
  6. //导入属性(这里不区分public还是private)
  7. [ImportMany]
  8. public List<string> InputString { get; set; }
  9.  
  10. static void Main(string[] args)
  11. {
  12. Program pro = new Program();
  13. pro.Compose();
  14. if (pro.Services != null)
  15. {
  16. foreach (var service in pro.Services)
  17. {
  18. var ss = (IBookService)service;
  19. Console.WriteLine(ss.GetBookName());
  20. }
  21. foreach (var str in pro.InputString)
  22. {
  23. Console.WriteLine(str);
  24. }
  25. }
  26. Console.Read();
  27. }
  28. //宿主MEF并组合部件
  29. private void Compose()
  30. {
  31. //获取包含当前执行的代码的程序集
  32. var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  33. CompositionContainer container = new CompositionContainer(catalog);
  34. //将部件(part)和宿主程序添加到组合容器
  35. container.ComposeParts(this);
  36. }
  37. }

最后的输出结果为:

下面说导出方法吧,同理无论是公有方法还是私有方法都是可以导出的,MusicBook代码如下:

  1. //导出公用方法(无参数)
  2. [Export(typeof (Func<string>))]
  3. public string PublicGetBookName()
  4. {
  5. return "PublicMathBook";
  6. }
  7. //导出私有方法(有参数)
  8. [Export(typeof (Func<int,string>))]
  9. public string PrivateGetBookName(int count)
  10. {
  11. return string.Format("我看到一个好书,我买了{0}本", count);
  12. }
  1. class Program
  2. {
  3. [ImportMany("MusicBook")]
  4. public IEnumerable<object> Services { get; set; }
  5.  
  6. //导入属性(这里不区分public还是private)
  7. [ImportMany]
  8. public List<string> InputString { get; set; }
  9.  
  10. //导入无参数方法
  11. [Import]
  12. public Func<string> methodWithoutPara { get; set; }
  13. //导入有参数的方法
  14. [Import]
  15. public Func<int, string> methodWithPara { get; set; }
  16.  
  17. static void Main(string[] args)
  18. {
  19. Program pro = new Program();
  20. pro.Compose();
  21. if (pro.Services != null)
  22. {
  23. foreach (var service in pro.Services)
  24. {
  25. var ss = (IBookService)service;
  26. Console.WriteLine(ss.GetBookName());
  27. }
  28. foreach (var str in pro.InputString)
  29. {
  30. Console.WriteLine(str);
  31. }
  32. }
  33. //调用无参数的方法
  34. if (pro.methodWithPara != null)
  35. {
  36. Console.WriteLine(pro.methodWithoutPara());
  37. }
  38.  
  39. //调用有参数的方法
  40. if (pro.methodWithoutPara!=null)
  41. {
  42. Console.WriteLine(pro.methodWithPara(5));
  43. }
  44. Console.Read();
  45. }
  46. //宿主MEF并组合部件
  47. private void Compose()
  48. {
  49. //获取包含当前执行的代码的程序集
  50. var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  51. CompositionContainer container = new CompositionContainer(catalog);
  52. //将部件(part)和宿主程序添加到组合容器
  53. container.ComposeParts(this);
  54. }
  55. }

导入导出方法用到了Func<T>委托,当然没有返回值的话可以用Action<T>委托。

------------------------------------------------MEF在分层架构中的使用---------------------------------------------

前面讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻。如果没有看过前面的文章,请到我的博客首页查看。

  前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要采用分层架构,就拿最简单的三层架构来说吧,我们通常把业务逻辑写在DLL中,现在就来写一个例子,看看如何在不编译整个项目的情况下,轻松的实现扩展。先透露一下,我们只要添加一个DLL就可以了。

  这里就以银行为例子吧,首先新建一个控制台项目,叫MEF_2吧,然后建一个类库写接口,然后再建一个类库实现接口。项目结构如下:

其中MEF_2和BankOfChina都只引用接口项目,MEF_2不需要引用BankOfChina。

BankInterface的代码如下,做个简单实例,写几个方法测试一下:

  1. public interface ICard
  2. {
  3. //账户金额
  4. double Money { get; set; }
  5. //获取账户信息
  6. string GetCountInfo();
  7. //存钱
  8. void SaveMoney(double money);
  9. //取钱
  10. void CheckOutMoney(double money);
  11. }

这里添加一个中国银行卡,实现接口,引用命名空间什么的不再重复说了,不懂看前面的文章,代码如下:

  1. namespace BankOfChina
  2. {
  3. [Export(typeof(ICard))]
  4. public class ZHCard : ICard
  5. {
  6. public string GetCountInfo()
  7. {
  8. return "中国银行";
  9. }
  10. public double Money { get; set; }
  11.  
  12. public void SaveMoney(double money)
  13. {
  14. this.Money += money;
  15. }
  16.  
  17. public void CheckOutMoney(double money)
  18. {
  19. this.Money -= money;
  20. }
  21.  
  22. }
  23. }

主程序中的代码:

  1. namespace MEF_2
  2. {
  3. class Program
  4. {
  5. [ImportMany(typeof(ICard))]
  6. public IEnumerable<ICard> cards { get; set; }
  7.  
  8. static void Main(string[] args)
  9. {
  10. Program pro = new Program();
  11. pro.Compose();
  12. foreach (var c in pro.cards)
  13. {
  14. Console.WriteLine(c.GetCountInfo());
  15. }
  16. Console.Read();
  17. }
  18.  
  19. private void Compose()
  20. {
  21. var catalog = new DirectoryCatalog("MyCards");
  22. var container = new CompositionContainer(catalog);
  23. container.ComposeParts(this);
  24. }
  25. }
  26. }

现在,我们知道只有一种银行卡,及中国银行的,注意我标红的代码,这里是一个目录,及主程序所在目录的Cards(bin-debug-Cards)文件夹(我们需要提前建立好),我们把生成的BankOfChian.dll拷贝到这个文件夹下,然后运行才可以正确输出信息(毕竟我们没有引用那个项目),如图:

此时我们看到输出结果为"中国银行"。

到了这里相信大家已经明白了,如果现在需求改变了,需要支持建行、农行等银行卡,怎么办呢?通常我们要改项目,把整个项目都编译再重新发布。但是现在不需要这么做了,我们只需要添加一个类库项目,把生成的dll拷贝到Cards目录下即可。

我们在这个解决方案下继续添加一个类库项目,实现ICard接口,代码如下:

  1. namespace NongHang
  2. {
  3. [Export(typeof(ICard))]
  4. public class NHCard:ICard
  5. {
  6. public double Money { get; set; }
  7.  
  8. public string GetCountInfo()
  9. {
  10. return "中国农业银行";
  11. }
  12.  
  13. public void SaveMoney(double money)
  14. {
  15. this.Money += money;
  16. }
  17.  
  18. public void CheckOutMoney(double money)
  19. {
  20. this.Money -= money;
  21. }
  22. }
  23. }

点击右键编译,把生成的dll拷贝到Cards目录下面,运行看到如下结果:

再看看Cards目录中,现在你添加几个dll,就会显示多少条信息了。

------------------------------------------------MEF的高级用法 MEF中如何访问某个具体的对象-----------------------------------

前面我们讲过在导出的时候,可以在[Export()]注解中加入名称标识,从而识别某个具体的对象,然而这种方法只是用于页面初始化的时候就行过滤,页面打开后没有导入的就再也导入不了了,就是说我们不能在导入的集合中分辨各自的不同,所有导入的类都是没有标识的。

待续...

MEF学习笔记的更多相关文章

  1. C#可扩展编程之MEF学习笔记(五):MEF高级进阶

    好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用 ...

  2. C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻

    前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...

  3. C#可扩展编程之MEF学习笔记(三):导出类的方法和属性

    前面说完了导入和导出的几种方法,如果大家细心的话会注意到前面我们导出的都是类,那么方法和属性能不能导出呢???答案是肯定的,下面就来说下MEF是如何导出方法和属性的. 还是前面的代码,第二篇中已经提供 ...

  4. C#可扩展编程之MEF学习笔记(二):MEF的导出(Export)和导入(Import)

    上一篇学习完了MEF的基础知识,编写了一个简单的DEMO,接下来接着上篇的内容继续学习,如果没有看过上一篇的内容, 请阅读:http://www.cnblogs.com/yunfeifei/p/392 ...

  5. C#可扩展编程之MEF学习笔记(一):MEF简介及简单的Demo

    在文章开始之前,首先简单介绍一下什么是MEF,MEF,全称Managed Extensibility Framework(托管可扩展框架).单从名字我们不难发现:MEF是专门致力于解决扩展性问题的框架 ...

  6. C#可扩展编程之MEF学习笔记(五):MEF高级进阶(转)

    好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用 ...

  7. C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻(转)

    前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...

  8. C#可扩展编程之MEF学习

    MEF系列文章: C#可扩展编程之MEF学习笔记(一):MEF简介及简单的Demo C#可扩展编程之MEF学习笔记(二):MEF的导出(Export)和导入(Import) C#可扩展编程之MEF学习 ...

  9. [转]MEF学习

    MEF学习 :http://www.cnblogs.com/comsokey/p/MEF1.html C#可扩展编程之MEF学习笔记(一):MEF简介及简单的Demo C#可扩展编程之MEF学习笔记( ...

随机推荐

  1. Struts1中ActionForward的技巧介绍

    ActionForward是做什么的?他是用来封装转发和重定向路径的. 在struts- config.xml中<forward name="error" path=&quo ...

  2. 信号之sigsetjmp和siglongjmp函数

    在信号处理程序中经常调用longjmp函数以返回到程序的主循环中,而不是从该处理程序返回. 但是,调用longjmp有一个问题.当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号 ...

  3. windows身份验证模式和SQL server身份验证模式 有什么不同

    两个验证方式是有明显不同的. 主要集中在信任连接和非信任连接. windows 身份验证相对于混合模式更加安全,使用本连接模式时候,sql不判断sa密码,而仅根据用户的windows权限来进行身份验证 ...

  4. Simulate android behaviors on win32

    To make debugging android games on win32 more convenience, we added some simulate actions to win32 p ...

  5. Using zend-paginator in your Album Module

    Using zend-paginator in your Album Module TODO Update to: follow the changes in the user-guide use S ...

  6. iOS 图片加载框架- SDWebImage 解读

    在iOS的图片加载框架中,SDWebImage可谓是占据大半壁江山.它支持从网络中下载且缓存图片,并设置图片到对应的UIImageView控件或者UIButton控件.在项目中使用SDWebImage ...

  7. Maven学习小结(一 初探)

    1.下载Maven,解压并设置到环境变量中 https://maven.apache.org/download.cgi 需要先设置“JAVA_HOME”,否则报错: 之后查看Maven版本成功: 1. ...

  8. MySQL(17):Select-union(联合查询)使用注意事项

    1. 需求: 获得0115班所有的代课教师代课天数,结果按照升序排序:同时获得0228班,结果按照降序排序. (1)首先查询原来的0115班和0228班所有代课天数,如下:       (2)使用un ...

  9. 关于JFace的自定义对话框(Dialog类)

    仅仅是使用MessageDialog,InputDialog等JFace中现成的对话框类是无法满足实际项目开发需要的. 很多时候都需要自己定制对话框,自定义对话框只要在Dialog类的基础上作扩展就行 ...

  10. HashMap、HashSet源代码分析其 Hash 存储机制

    集合和引用 就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java 对象放入数组中,只是把对象的引用放入数组中,每个数组元素都是一个引用变量. 实际上,HashSet ...