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使用实例
接口定义:
- namespace Interface
- {
- public interface IBookService
- {
- void GetBookName();
- }
- }
针对于接口的3个实现,如下代码所示:
- using System;
- using System.ComponentModel.Composition;
- using Interface;
-
-
- namespace ComputerBookServiceImp
- {
- [Export(typeof(IBookService))]
- public class ComputerBookService : IBookService
- {
- public void GetBookName()
- {
- Console.WriteLine("Computer Book");
- }
- }
- }
上面代码除了对接口的实现以外,有两点需要说明的:
1、项目中引用了System.ComponentModel.Composition程序集,使用MEF必须使用该程序集。
2、使用了Export特性标记了ComputerBookService类,并且声明其类型为IBookServivce,通过此特性说明该类是MEF的一个导出部件。
其他两个版本的实现和上面雷同,代码如下:
HistoryBookServiceImp
- using System;
- using System.ComponentModel.Composition;
- using Interface;
-
-
- namespace HistoryBookServiceImp
- {
- [Export(typeof(IBookService))]
- public class HistoryBookService : IBookService
- {
- public void GetBookName()
- {
- Console.WriteLine("History Book");
- }
- }
- }
MathBookServiceImp
- using System;
- using System.ComponentModel.Composition;
- using Interface;
-
-
- namespace MathBookServiceImp
- {
- [Export(typeof(IBookService))]
- public class MathBookService : IBookService
- {
- public void GetBookName()
- {
- Console.WriteLine("Math Book");
- }
- }
- }
然后在修改控制台应用程序Proggram类的代码如下所示:
- using System.ComponentModel.Composition;
- using System.ComponentModel.Composition.Hosting;
- using Interface;
-
-
- namespace HostApp
- {
- class Program
- {
- static void Main(string[] args)
- {
- Program program = new Program();
- program.Compose();
- program.BookService.GetBookName();
- }
-
-
- [Import]
- public IBookService BookService { get; set; }
-
-
-
-
-
- public void Compose()
- {
- DirectoryCatalog directoryCatalog = new DirectoryCatalog("imps");
- var container = new CompositionContainer(directoryCatalog);
- container.ComposeParts(this);
- }
- }
- }
上面的代码有3点需要说明:
1、通过Import特性标记BookService为一个导入属性,这样MEF的容器在进行宿主和部件组装的时候,将会查找与之匹配的导出部件,然后进行组装。上述代码所查找的是类型与BooService类型相同的导出部件。
2、通过使用DirectoryCatalog类指明要查找的导出部件位于应用程序根目录下的imps子文件夹中。
3、通过CompositionContainer的扩展方法ComposeParts()组装宿主程序和导出部件。在该方法执行前,BookService属性值为null,运行后,如果找到匹配的导出部件,则创建了该导出部件的一个实例。
MEF导入导出遵守协议一致
包含协议名和类型的导出:
- [Export("SQL", typeof(ILogger))]
- public class SqlLogger : ILogger
- {
- public void WriteLog(string message)
- {
- Console.WriteLine("SQL Logger => {0}", message);
- }
- }
其中Export的第一个参数是协议名称,第二个参数则是导出的类型。对应的导入代码如下:
- [Import("SQL", typeof(ILogger))]
- public ILogger SqlLogger { get; set; }
只包含导出协议名的导出:
- [Export("Oracal")]
- public class OracalLogger : ILogger
- {
- public void WriteLog(string message)
- {
- Console.WriteLine("Oracal Logger => {0}", message);
- }
- }
上述导出协议并未指定导出类型,因此就以所修饰类作为其导出类型,即OracalLogger。相应的导入代码为:
- [Import("Oracal")]
- public OracalLogger OracalLogger { get; set; }
其中属性的类型是OracalLogger,而不是ILogger。如果类型为ILogger,导入就会失败。
只包含导出类型的导出:
- [Export(typeof(ILogger))]
- public class TxtLogger : ILogger
- {
- public void WriteLog(string message)
- {
- Console.WriteLine("Text Logger => {0}", message);
- }
- }
上述导出协议未指定导出协议名称,只指定了导出类型。则相应的导入代码为:
- [Import(typeof(ILogger))]
- public ILogger TextLogger { get; set; }
既不包含导出类型,也不包含导出协议名称的导出:
- [Export]
- public class XmlLogger : ILogger
- {
- public void WriteLog(string message)
- {
- Console.WriteLine("Xml Logger => {0}", message);
- }
- }
上述代码的导出协议名为默认协议,类型则与所修改的类相同,即XmlLogger,与之匹配的导入代码应该如下所示:
- [Import]
- public XmlLogger XmlLogger { get; set; }
务必确保导入属性XmlLogger的属性类型与导出特性修饰的类型保持一致,否则导入失败。
MEF延迟加载
延迟加载在导出部件处的设置没什么变化,保持原有的设置即可。如下代码所示:
- [Export("Oracal", typeof(ILogger))]
- public class OracalLogger : ILogger
- {
- public void WriteLog(string message)
- {
- Console.WriteLine("Oracal Logger => {0}", message);
- }
- }
在导入的时候,需要使用.NET提供的Lazy类来完成。代码如下:
- [Import("Oracal", typeof(ILogger))]
- public Lazy<ILogger> OracalLogger { get; set; }
通过Lazy封装的对象即自动提供了延迟加载的机制。访问对象的方式也略微有所改变,当然不能直接通过属性(上例中的OracalLogger)进行访问,而是需要通过Lazy<T>.Value来访问,此属性值包含的即使延迟加载的对象。
另外Lazy<T>.IsValueCreated属性提供了判断对象是否加载的功能,如果为true则表示对象已加载,否则为未加载。
MEF元数据
关于MEF的数据的使用,首先需要给导出部件添加ExportMetadata特性,用来设置要添加的元数据。如下面代码所示:
- [ExportMetadata("Name", "Xml")]
- [ExportMetadata("Description", "使用的是XML来记录日志")]
- [Export("Xml", typeof(ILogger))]
- public class XmlLogger : ILogger
- {
- public void WriteLog(string message)
- {
- Console.WriteLine("Xml Logger => {0}", message);
- }
- }
如上代码所示,首先ExportMetadata特性可以被重复的添加到多个多出部件,另外,该特性的两个参数非常的类似于键值对,第一个参数为元数据的名称,第二个参数则是元数据的实际内容。
至此,导出部件所需要做的修改就算是完成了。接下来,就该修改导入了。如下代码所示:
- [Import("Xml", typeof(ILogger))]
- public Lazy<ILogger, IMetadata> XmlLogger { get; set; }
如上代码所示,仍然使用的Lazy类。其中第二个泛型参数表示的就是元数据的接口。定义大致如下:
- public interface IMetadata
- {
- string Description { get; }
- string Name { get; }
- }
仔细对比后会发现,该接口中的成员名称和导出部件中的元数据名称是如此的一致。实是上不仅其名称要保持一致,连数据类型也要保持一致,否则导入的时候将会引发异常。
经过如上的代码修改后,通过导入元素(上例中的XmlLogger属性)会有一个名为Metadata的属性,即Lazy<T, TMeta>.Metadata,通过它即可访问导出部件中的各个元数据了。
如下代码所示:
- Console.WriteLine(program.XmlLogger.Metadata.Name);
- Console.WriteLine(program.XmlLogger.Metadata.Description);
MEF多部件导入ImportMany
部件的导出设置不做任何改变,将导入地方做类似如下的修改:
- [ImportMany(typeof(ILogger))]
- public IEnumerable<ILogger> Loggers { get; set; }
上述代码和单个部件的导入有两点区别:
1、使用ImportMany特性,而不是Import特性。
2、使用的是IEnumerable<T>类型来封装导入的部件。因为,导入的可能是多个部件,需要一种集合的方式来承载。
导入元素做了如上的修改后,调用时只需要遍历即可访问每个导出部件。如下代码所示:
推荐链接:http://blog.csdn.net/gjysk/article/details/44648505
请发表评论