Managed Extensibility Framework (MEF)


什么是 MEF?

   Managed Extensibility Framework 即 MEF 是用于创建轻量、可扩展应用程序的库。 它让应用程序开发人员得以发现和使用扩展且无需配置。 它还让扩展开发人员得以轻松地封装代码并避免脆弱的紧密依赖性。 MEF 让扩展不仅可在应用程序内重复使用,还可以跨程序重复使用。
 

实例:

MEF 位于 ComponentModel.Composition 程序集中

添加 System.ComponentModel.Composition 和 System.ComponentModel.Composition.Hosting 的项目引用

ImportAttribute  属性声明了某些操作为导入;在撰写对象时,它将由组合引擎进行填写。

ImportAttribute 属性由且只能由 ExportAttribute 填写。 如果可用数目超过一,则组合引擎生成错误。 你可以使用 ImportManyAttribute 属性来创建可由任意数目的导出填写的导入

Lazy<T, TMetadata> 是由 MEF 提供来保存对导出的间接引用的类型。 除了导出对象本身,你还可以获取导出元数据或描述导出对象的信息。 每个Lazy<T, TMetadata> 都包含一个代表实际操作的 IOperation 对象和一个代表元数据的 IOperationData 对象。

ExportAttribute 属性函数和之前一致。ExportMetadataAttribute 属性将采用名称值对形式的元数据附加到导出

DirectoryCatalog 将把在扩展目录中的所有程序集中发现的部件添加到撰写容器中。

MEF 组合模型的核心是包含所有可用部件并执行撰写的撰写容器。 (它是对导入到导出进行的匹配。) 撰写容器最常用的类型是CompositionContainer

MEF使用实例


接口定义:

  1. namespace Interface
  2. {
  3. public interface IBookService
  4. {
  5. void GetBookName();
  6. }
  7. }

针对于接口的3个实现,如下代码所示:

  1. using System;
  2. using System.ComponentModel.Composition;
  3. using Interface;
  4. namespace ComputerBookServiceImp
  5. {
  6. [Export(typeof(IBookService))]
  7. public class ComputerBookService : IBookService
  8. {
  9. public void GetBookName()
  10. {
  11. Console.WriteLine("Computer Book");
  12. }
  13. }
  14. }

上面代码除了对接口的实现以外,有两点需要说明的:
1、项目中引用了System.ComponentModel.Composition程序集,使用MEF必须使用该程序集。
2、使用了Export特性标记了ComputerBookService类,并且声明其类型为IBookServivce,通过此特性说明该类是MEF的一个导出部件。

其他两个版本的实现和上面雷同,代码如下:

HistoryBookServiceImp

  1. using System;
  2. using System.ComponentModel.Composition;
  3. using Interface;
  4. namespace HistoryBookServiceImp
  5. {
  6. [Export(typeof(IBookService))]
  7. public class HistoryBookService : IBookService
  8. {
  9. public void GetBookName()
  10. {
  11. Console.WriteLine("History Book");
  12. }
  13. }
  14. }

MathBookServiceImp

  1. using System;
  2. using System.ComponentModel.Composition;
  3. using Interface;
  4. namespace MathBookServiceImp
  5. {
  6. [Export(typeof(IBookService))]
  7. public class MathBookService : IBookService
  8. {
  9. public void GetBookName()
  10. {
  11. Console.WriteLine("Math Book");
  12. }
  13. }
  14. }

然后在修改控制台应用程序Proggram类的代码如下所示:

  1. using System.ComponentModel.Composition;
  2. using System.ComponentModel.Composition.Hosting;
  3. using Interface;
  4. namespace HostApp
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. Program program = new Program();
  11. program.Compose();
  12. program.BookService.GetBookName();
  13. }
  14. [Import]
  15. public IBookService BookService { get; set; }
  16. /// <summary>
  17. /// 通过容器对象将宿主和部件组装到一起。
  18. /// </summary>
  19. public void Compose()
  20. {
  21. DirectoryCatalog directoryCatalog = new DirectoryCatalog("imps");
  22. var container = new CompositionContainer(directoryCatalog);
  23. container.ComposeParts(this);
  24. }
  25. }
  26. }

上面的代码有3点需要说明:
1、通过Import特性标记BookService为一个导入属性,这样MEF的容器在进行宿主和部件组装的时候,将会查找与之匹配的导出部件,然后进行组装。上述代码所查找的是类型与BooService类型相同的导出部件。
2、通过使用DirectoryCatalog类指明要查找的导出部件位于应用程序根目录下的imps子文件夹中。
3、通过CompositionContainer的扩展方法ComposeParts()组装宿主程序和导出部件。在该方法执行前,BookService属性值为null,运行后,如果找到匹配的导出部件,则创建了该导出部件的一个实例。

MEF导入导出遵守协议一致


包含协议名和类型的导出:

  1. [Export("SQL", typeof(ILogger))]
  2. public class SqlLogger : ILogger
  3. {
  4. public void WriteLog(string message)
  5. {
  6. Console.WriteLine("SQL Logger => {0}", message);
  7. }
  8. }

其中Export的第一个参数是协议名称,第二个参数则是导出的类型。对应的导入代码如下:

  1. [Import("SQL", typeof(ILogger))]
  2. public ILogger SqlLogger { get; set; }

只包含导出协议名的导出:

  1. [Export("Oracal")]
  2. public class OracalLogger : ILogger
  3. {
  4. public void WriteLog(string message)
  5. {
  6. Console.WriteLine("Oracal Logger => {0}", message);
  7. }
  8. }

上述导出协议并未指定导出类型,因此就以所修饰类作为其导出类型,即OracalLogger。相应的导入代码为:

  1. [Import("Oracal")]
  2. public OracalLogger OracalLogger { get; set; }

其中属性的类型是OracalLogger,而不是ILogger。如果类型为ILogger,导入就会失败。

只包含导出类型的导出:

  1. [Export(typeof(ILogger))]
  2. public class TxtLogger : ILogger
  3. {
  4. public void WriteLog(string message)
  5. {
  6. Console.WriteLine("Text Logger => {0}", message);
  7. }
  8. }

上述导出协议未指定导出协议名称,只指定了导出类型。则相应的导入代码为:

  1. [Import(typeof(ILogger))]
  2. public ILogger TextLogger { get; set; }

既不包含导出类型,也不包含导出协议名称的导出:

  1. [Export]
  2. public class XmlLogger : ILogger
  3. {
  4. public void WriteLog(string message)
  5. {
  6. Console.WriteLine("Xml Logger => {0}", message);
  7. }
  8. }

上述代码的导出协议名为默认协议,类型则与所修改的类相同,即XmlLogger,与之匹配的导入代码应该如下所示:

  1. [Import]
  2. public XmlLogger XmlLogger { get; set; }

务必确保导入属性XmlLogger的属性类型与导出特性修饰的类型保持一致,否则导入失败。

MEF延迟加载


延迟加载在导出部件处的设置没什么变化,保持原有的设置即可。如下代码所示:

[csharp] view plain copy

 

  1. [Export("Oracal", typeof(ILogger))]
  2. public class OracalLogger : ILogger
  3. {
  4. public void WriteLog(string message)
  5. {
  6. Console.WriteLine("Oracal Logger => {0}", message);
  7. }
  8. }

在导入的时候,需要使用.NET提供的Lazy类来完成。代码如下:

[csharp] view plain copy

 

  1. [Import("Oracal", typeof(ILogger))]
  2. public Lazy<ILogger> OracalLogger { get; set; }

通过Lazy封装的对象即自动提供了延迟加载的机制。访问对象的方式也略微有所改变,当然不能直接通过属性(上例中的OracalLogger)进行访问,而是需要通过Lazy<T>.Value来访问,此属性值包含的即使延迟加载的对象。
另外Lazy<T>.IsValueCreated属性提供了判断对象是否加载的功能,如果为true则表示对象已加载,否则为未加载。

MEF元数据


关于MEF的数据的使用,首先需要给导出部件添加ExportMetadata特性,用来设置要添加的元数据。如下面代码所示:

[csharp] view plain copy

 

  1. [ExportMetadata("Name", "Xml")]
  2. [ExportMetadata("Description", "使用的是XML来记录日志")]
  3. [Export("Xml", typeof(ILogger))]
  4. public class XmlLogger : ILogger
  5. {
  6. public void WriteLog(string message)
  7. {
  8. Console.WriteLine("Xml Logger => {0}", message);
  9. }
  10. }

如上代码所示,首先ExportMetadata特性可以被重复的添加到多个多出部件,另外,该特性的两个参数非常的类似于键值对,第一个参数为元数据的名称,第二个参数则是元数据的实际内容。
至此,导出部件所需要做的修改就算是完成了。接下来,就该修改导入了。如下代码所示:

[csharp] view plain copy

 

  1. [Import("Xml", typeof(ILogger))]
  2. public Lazy<ILogger, IMetadata> XmlLogger { get; set; }

如上代码所示,仍然使用的Lazy类。其中第二个泛型参数表示的就是元数据的接口。定义大致如下:

[csharp] view plain copy

 

  1. public interface IMetadata
  2. {
  3. string Description { get; }
  4. string Name { get; }
  5. }

仔细对比后会发现,该接口中的成员名称和导出部件中的元数据名称是如此的一致。实是上不仅其名称要保持一致,连数据类型也要保持一致,否则导入的时候将会引发异常。

经过如上的代码修改后,通过导入元素(上例中的XmlLogger属性)会有一个名为Metadata的属性,即Lazy<T, TMeta>.Metadata,通过它即可访问导出部件中的各个元数据了。
如下代码所示:

[csharp] view plain copy

 

    1. Console.WriteLine(program.XmlLogger.Metadata.Name);
    2. Console.WriteLine(program.XmlLogger.Metadata.Description);

MEF多部件导入ImportMany


部件的导出设置不做任何改变,将导入地方做类似如下的修改:

[csharp] view plain copy

 

  1. [ImportMany(typeof(ILogger))]
  2. public IEnumerable<ILogger> Loggers { get; set; }

上述代码和单个部件的导入有两点区别:
1、使用ImportMany特性,而不是Import特性。
2、使用的是IEnumerable<T>类型来封装导入的部件。因为,导入的可能是多个部件,需要一种集合的方式来承载。

导入元素做了如上的修改后,调用时只需要遍历即可访问每个导出部件。如下代码所示:

[csharp] view plain copy

 

    1. static void Main(string[] args)
    2. {
    3. Program program = new Program();
    4. program.Compose();
    5. foreach (ILogger logger in program.Loggers)
    6. {
    7. logger.WriteLog("Hello World!");
    8. }
    9. Console.ReadLine();
    10. }

MEF目录服务(Catalog)


在MEF中,所谓的目录(Catalog)是不同于文件系统中的目录(Directory),而它的作用就是告知MEF框架可以在什么地方去查找导出部件,比如:
AssemblyCatalog:在指定的程序集中查找导出部件。
DirectoryCatalog:在指定的文件目录中查找导出部件,在构造函数中可传递文件目录,可以是绝对路径或相对路径。
代码如下:

[csharp] view plain copy

 

  1. /// <summary>
  2. /// 通过容器对象将宿主和部件组装到一起。
  3. /// </summary>
  4. public void Compose()
  5. {
  6. AggregateCatalog aggregateCatalog = new AggregateCatalog();
  7. AssemblyCatalog assemblyCatalog = new AssemblyCatalog(typeof(Program).Assembly);
  8. DirectoryCatalog directoryCatalog = new DirectoryCatalog("imps");
  9. aggregateCatalog.Catalogs.Add(assemblyCatalog);
  10. aggregateCatalog.Catalogs.Add(directoryCatalog);
  11. var container = new CompositionContainer(aggregateCatalog);
  12. container.ComposeParts(this);
  13. }

上述代码中有一个AggregateCatalog的目录类,这是一个聚合目录,它可以将多个目录组织在一起,比如上列中的AssemblyCatalog和DirectoryCatalog。这样,MEF就会在多个地方去搜寻匹配的导出部件。

MEF自定义部件目录类Catalog


在MEF中,除了可以使用自身提供的注入AggregateCatalog、AssemblyCatalog、DirectoryCatalog这样的目录类以外,也可以自己定义目录类。
自定义目录类需要继承自ComposablePartCatalog类,并实现接口INotifyComposablePartCatalogChanged即可。如下面所示的代码:

  1. public class FilteredCatalog : ComposablePartCatalog, INotifyComposablePartCatalogChanged
  2. {
  3. #region Private Fields
  4. private readonly ComposablePartCatalog m_ComposablePartCatalog;
  5. private readonly INotifyComposablePartCatalogChanged m_NotifyComposablePartCatalogChanged;
  6. private readonly IQueryable<ComposablePartDefinition> m_Parts;
  7. #endregion
  8. #region Constructors
  9. /// <summary>
  10. /// 默认构造函数。
  11. /// </summary>
  12. /// <param name="composablePartCatalog">包含了所有导出部件的目录Catalog。</param>
  13. /// <param name="expression">筛选条件表达式。</param>
  14. public FilteredCatalog(ComposablePartCatalog composablePartCatalog, Expression<Func<ComposablePartDefinition, bool>> expression)
  15. {
  16. m_ComposablePartCatalog = composablePartCatalog;
  17. m_NotifyComposablePartCatalogChanged = composablePartCatalog as INotifyComposablePartCatalogChanged;
  18. m_Parts = composablePartCatalog.Parts.Where(expression);
  19. }
  20. #endregion
  21. #region INotifyComposablePartCatalogChanged
  22. /// <summary>
  23. /// 部件目录Catalog已经改变后触发的事件。
  24. /// </summary>
  25. public event EventHandler<ComposablePartCatalogChangeEventArgs> Changed
  26. {
  27. add
  28. {
  29. if (m_NotifyComposablePartCatalogChanged != null)
  30. {
  31. m_NotifyComposablePartCatalogChanged.Changed += value;
  32. }
  33. }
  34. remove
  35. {
  36. if (m_NotifyComposablePartCatalogChanged != null)
  37. {
  38. m_NotifyComposablePartCatalogChanged.Changed -= value;
  39. }
  40. }
  41. }
  42. /// <summary>
  43. /// 部件目录Catalog正在发生改变时触发的事件。
  44. /// </summary>
  45. public event EventHandler<ComposablePartCatalogChangeEventArgs> Changing
  46. {
  47. add
  48. {
  49. if (m_NotifyComposablePartCatalogChanged != null)
  50. {
  51. m_NotifyComposablePartCatalogChanged.Changing += value;
  52. }
  53. }
  54. remove
  55. {
  56. if (m_NotifyComposablePartCatalogChanged != null)
  57. {
  58. m_NotifyComposablePartCatalogChanged.Changing -= value;
  59. }
  60. }
  61. }
  62. #endregion
  63. #region ComposablePartCatalog
  64. /// <summary>
  65. /// 获取目录中包含的部件定义。经过构造函数中的表达式过滤后,已经是传入目录Catalog对象中的一部分导出部件了。
  66. /// </summary>
  67. public override IQueryable<ComposablePartDefinition> Parts
  68. {
  69. get { return m_Parts; }
  70. }
  71. #endregion
  72. }

上述代码中概括来说包含如下几点内容:
1、构造函数传递了基础目录Catalog对象,这个目录对象可能包含了很多的导出部件,我们要实现的目录过滤类FilteredCatalog就是基于这个目录进行过滤的,它是个全集,而FilteredCatalog是它的子集。另外一个参数是过滤表达式,过滤条件由调用者来编写,至于内部过滤办法实际还是LINQ提供的Where()方法。
2、对于接口INotifyComposablePartCatalogChanged的实现,实际上是和基础目录Catalog对象的事件关联在一起,即当基础目录对象发生改变时,目录过滤类FilteredCatalog也将会收到相应的通知。
3、重写了基类ComposablePartCatalog的Parts集合属性,该属性返回的就是该目录中包含部件定义,凡是在目录中需要被暴露的部件定义都是通过该集合返回的。因此,上述代码中将过滤后的部件定义通过该属性返回。

定义好了过滤类,接下来就是如何使用它了。

三、使用自定义目录类Catalog
如下代码所示:

[csharp] view plain copy

 

  1. // 获取所需的部件。
  2. DirectoryCatalog catalog = new DirectoryCatalog("controls");
  3. CompositionContainer container = new CompositionContainer(catalog);
  4. // 过滤Catalog,生成子组合容器。
  5. FilteredCatalog filteredCatalog = new FilteredCatalog(catalog,
  6. o=>o.Metadata.ContainsKey("UC") && o.Metadata["UC"].ToString() == "BB");
  7. CompositionContainer filteredContainer = new CompositionContainer(filteredCatalog, container);
  8. UserControl userControl = filteredContainer.GetExportedValue<UserControl>();
  9. this.MainContentControl.Content = userControl;

首先通过DirectoryCatalog类获取到应用程序根目录下controls子文件夹中的所有部件定义,并以此生成顶级组合容器container(类型为CompositionContainer)。
然后使用自定义目录过滤类FilteredCatalog对DirectoryCatalog目录中的部件定义进行过滤,并生成子组合容器filteredContainer(类型为CompositionContainer)。
最后通过组合容器的GetExportedValue<T>()方法获取指定协议类型的导出部件。需要说明的是,如果组合容器中没有对应协议类型的导出部件则会引发异常。

MEF部件创建策略


MEF提供的导出部件的三种创建策略,分别如下:
CreationPolicy.Any:表示可共享或不共享,部件的实例用MEF容器根据不同的请求需求自动控制。
CreationPolicy.Shared表示共享部件,既Shared类型的插件部件可以在多个MEF组合容器中共用,而且多次请求该部件返回的其实是同一个对象。
CreationPolicy.NonShared类型,表示不共享部件实例,每当有新的请求就会创建一个新的对象实例。

三、关键代码
下面介绍了如何在导出部件中设置其创建策略,以及导入的时候如何使用。
通过为导出部件添加PartCreationPolicyAttribute特性,可指定其创建策略。代码如下:

[csharp] view plain copy

 

  1. [PartCreationPolicy(CreationPolicy.NonShared)]
  2. [Export(typeof(UserControl))]
  3. public partial class UserControlBB : UserControl
  4. {
  5. public UserControlBB()
  6. {
  7. InitializeComponent();
  8. }
  9. }

在导出部件的时候,可设置导入指定创建策略的部件,比如下面的代码,只导入创建策略为CreationPolicy.Shared的导出部件。

[csharp] view plain copy

 

    1. [ImportMany(RequiredCreationPolicy = CreationPolicy.Shared)]
    2. public List<UserControl> UserControls { get; set; }

推荐链接:http://blog.csdn.net/gjysk/article/details/44648505

C# 高级编程9 第30章MEF C#可扩展编程之MEF第2章(抄录)的更多相关文章

  1. C# 高级编程9 第30章MEF C#可扩展编程之MEF第一章

    MEF(Managed Extensibility Framework)是一个用于创建可扩展的轻型应用程序的库 利用该库轻松地封装代码,避免生成脆弱的硬依赖项. 通过 MEF,不仅可以在应用程序内重用 ...

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

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

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

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

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

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

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

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

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

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

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

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

  8. C#可扩展编程之MEF

    C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻 前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在 ...

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

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

随机推荐

  1. centos 升级linux内核

    =============================================== 2018/1/14_第1次修改                       ccb_warlock == ...

  2. OpenJDK,Oracle's OpenJDK,Oracle JDK的区别与选择

    OpenJDK 单纯的OpenJDK指的是JDK的源码,以GPL协议开源,由企业和社区开发者共同维护和开发. Oracle's OpenJDK Oracle started providing ope ...

  3. 洛谷P2812校园网络

    传送门啦 其实这个题只要读懂分析好题意就不是很难. 就是将一个有向图进行缩点操作,把一个强连通分量看成一个点,求入度为 0 的点和出度为 0 的点各有多少. 在这里先向大家推荐两个题目,建议大家先去看 ...

  4. 异步消息框架netty

    Netty-WebSocket长连接推送服务 http://blog.csdn.net/z69183787/article/details/52505249 http://blog.csdn.net/ ...

  5. thinkphp调试

    Sql调试

  6. 20155225 2016-2017-2 《Java程序设计》第一周学习总结

    20155225 2016-2017-2 <Java程序设计>第一周学习总结 教材学习内容总结 JVM(java virtual machine)就是java虚拟机,我的理解就是编译运行j ...

  7. #CSS 文本溢出部分显示省略号

    单行结尾溢出: 用text-overflow:ellipsis属性实现,当然还需要加宽度width属来兼容部分浏览. width:300px;overflow: hidden; text-overfl ...

  8. Azkaban(一)Azkaban的基础介绍

    一.为什么需要工作流调度器 1.一个完整的数据分析系统通常都是由大量任务单元组成: shell 脚本程序,java 程序,mapreduce 程序.hive 脚本等 2.各任务单元之间存在时间先后及前 ...

  9. day5模块学习--random模块

    Python中的random模块用于生成随机数 下面具体介绍random模块的功能:   1.random.random() #用于生成一个0到1的浮点数   随机浮点数:0<= n < ...

  10. 【51nod】1149 Pi的递推式

    题解 我们把这个函数的递归形式画成一张图,会发现答案是到每个出度为0的点的路径的方案数 这个可以用组合数算 记录一下P[i]为i减几次PI减到4以内 如果P[i + 1] > P[i],那么转向 ...