上一文中,我们大致明白了,利用MEF框架实现自动扫描并组装扩展组件的思路。本文我们继续前进,从最初的定义公共接口开始,一步步学会如何使用MEF。

在上一文中我们知道,对于每一个实现了公共规范的扩展组件,都需要进行导出,随后我们的主应用程序文件中会自动进行组装。这便产生了一个疑问:为什么需要导出?

如果大家还记得,以前我们用VC++写.dll文件时,都会把需要提供给别人调用的函数标记为导出函数,这样别人才能调用我们编写的函数。就好比我们的家,我们一般会有客厅,既然叫客厅,当然是展现给客人看的。有客人来了,我们会在客厅接待,当然我们不愿意让客人进入我们的卧室,那是较为隐私的地方。

因此,对于我们编写的扩展组件,我们要告诉MEF,哪些类应该被扫描,就像我们的网站一样,我们会过滤哪些页面允许搜索引擎进行抓取,一样的道理。

要把组件标记为可导出类型,需要在类型的定义代码上附加System.ComponentModel.Composition.ExportAttribute特性。我们可以看看ExportAttribute类的定义。

[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property|AttributeTargets.Field, AllowMultiple = true,

Inherited = false)]

public class ExportAttribute : Attribute

从定义我们看到,ExportAttribute特性可以用于类以及类的成员,能常我们会附加到整个类,以表示整个类型进行导出。

判断哪个导出类型符合组装容器导入的条件,是根据ContractName和ContractType属性。

ContractName我们可以在附加ExportAttribute时指定,也可以不指定。ContractType属性指定要导出的类型,如果不指定,默认就是当前要导出的类型。比如:

// 公共接口

public interface IMember

{

string GetMemberType();

}

[Export]

public class VipMember : IMember

{

public string GetMemberType()

{

return "VIP会员";

}

}

上面的例子,公共接口是IMember,类VipMember实现了该接口并标记为导出类型,但不指定ContractName和ContractType属性。在这种情况下,默认的协定类型为VipMember,特性附加到哪个类上,默认的导出类型就是该类的类型。

然后,我们再定义一个GenMember类。

[Export]

public class GenMember : IMember

{

public string GetMemberType()

{

return "普通会员";

}

}

这时候,对于GenMember类,导出的类型就是GenMember。

也许大家已经发现,这样定义导出类型缺点很明显,即没有一个通过的协定类型,这样一来,在组装扩展组件时就不能做到自动识别了,因为我们每扩展一个类就新一个协定类型(ContractType),这会导致主应用程序的代码需要反复修改,无法一劳永逸了。所以,通常来说,我们应当把ContractType设置为公共接口的类型,如上面例子中的IMember。故我们应该把代码改为:

[Export(typeof(IMember))]

public class VipMember : IMember

{

public string GetMemberType()

{

return "VIP会员";

}

}

[Export(typeof(IMember))]

public class GenMember : IMember

{

public string GetMemberType()

{

return "普通会员";

}

}

这样一改,就满足需求了,只要实现了IMember接口并且附加了ExportAttribute的类型都会被组装容器自动扫描,哪怕你扩展了99999999999999999999999999999个组件,它都能扫描并组装。

如果你希望组装容器在扫描类型时需要特定的类,可以在ExportAttribute中定义ContractName。这样就使得扫描类型的匹配条件变得更精准,缩小了查找范围。当然这样做也降低了智能性,因为在组装代码中,你还要去匹配协定名,这也使得主应用程序的代码会不断进行修改。

把类型导出之后,就可以提供给组装容器进行组装了。就拿我们上面的例子说吧,接下来我们对VipMember和GenMember类进行组装。

class Program

{

[Import(typeof(IMember))]

public List<IMember> AllMembers;

static void Main(string[] args)

{

// 发现类型的方式为当前程序集

AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

// 创建组装容器

CompositionContainer container = new CompositionContainer(catalog);

Program p = new Program();

// 开始组装

try

{

container.ComposeParts(p);

Console.WriteLine("---------- 测试调用 ----------");

foreach (IMember m in p.AllMembers)

{

Console.WriteLine(m.GetMemberType());

}

}

catch (CompositionException cex)

{

Console.WriteLine(cex.Message);

}

while (Console.ReadKey().Key != ConsoleKey.Escape) ;

}

}

因为我们对IMember扩展了两个类,为了能让它们全部导入,在Program类中定义了一个List<IMember>的字段,我们希望把所有导入的类型都放进这个List中。附加ImportAttribute时要与ExportAttribute相对应,前面我们只定义ContractType,所以这里导入时,我们依然使用ContractType来匹配。

这个应用程序看起来似乎没啥问题,估计可以运行了,于是我们可以按下F5看看结果。

噢,God,居然发生"奇迹"了,从异常信息中我们得知,ImportAttribute不能标记一次性导入多个类型的List的字段。那如何解决呢?莫急莫急,看看以下这个Attribute:

// 摘要:

// 指定属性、字段或参数应通过 System.ComponentModel.Composition.Hosting.CompositionContainer

// 对象用所有匹配的导出进行填充。

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]

public class ImportManyAttribute : Attribute, IAttributedImport

是的,这个Attribute专用于导入多个类型的,我们只要把代码改为这样就行了:

class Program

{

[ImportMany(typeof(IMember))]

public List<IMember> AllMembers;

……

再次运行,我们可以得到预期的结果了。

当今时代,我们什么东西都要绿色环保,我们的程序也不例外。上面的程序看起来是没什么大问题了。不过,如果我们要导入的类型可能会携带一些大型数据,我们要是能让它们延迟初始化那就节约了一些资源开销。虽然延迟初始化叫起来不太动听,不过也不算难以理解的概念,就好像你买体彩中了大奖,但提供方不是直接把一张张钞票拿给你,可能会先给你支票,然后,你凭支票去银行提钱。

这个延迟初始化也类似,它在声明时并不立即初始化,等到你使用的时候才初始化。System.Lazy<T>类将带领我们走向绿色环保的现代生活,它会等到你访问其Value属性时才初始化。于是,我们也把上面的代码环保一下。

class Program

{

[ImportMany(typeof(IMember))]

public List<Lazy<IMember>> AllMembers;

static void Main(string[] args)

{

……

try

{

container.ComposeParts(p);

Console.WriteLine("---------- 测试调用 ----------");

foreach (Lazy<IMember> lz in p.AllMembers)

{

Console.WriteLine(lz.Value.GetMemberType());

}

}

……

最后,提一个不重要的东西,我们代码中使用的类在

using System.ComponentModel.Composition;

using System.ComponentModel.Composition.Hosting;

请引用System.ComponentModel.Composition(.dll)程序集。

本文实例的完整代码如下:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.ComponentModel.Composition;

using System.ComponentModel.Composition.Hosting;

using System.Reflection;

namespace MEFExam

{

// 公共接口

public interface IMember

{

string GetMemberType();

}

[Export(typeof(IMember))]

public class VipMember : IMember

{

public string GetMemberType()

{

return "VIP会员";

}

}

[Export(typeof(IMember))]

public class GenMember : IMember

{

public string GetMemberType()

{

return "普通会员";

}

}

class Program

{

[ImportMany(typeof(IMember))]

public List<Lazy<IMember>> AllMembers;

static void Main(string[] args)

{

// 发现类型的方式为当前程序集

AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

// 创建组装容器

CompositionContainer container = new CompositionContainer(catalog);

Program p = new Program();

// 开始组装

try

{

container.ComposeParts(p);

Console.WriteLine("---------- 测试调用 ----------");

foreach (Lazy<IMember> lz in p.AllMembers)

{

Console.WriteLine(lz.Value.GetMemberType());

}

}

catch (CompositionException cex)

{

Console.WriteLine(cex.Message);

}

while (Console.ReadKey().Key != ConsoleKey.Escape) ;

}

}

}

最新文章

  1. android 有弹性的ScrollView 简单实现,与处理ScrollView和ListView,GridView之间的冲突
  2. linker command failed with exit code 1 (use -v to see invocation)解决办法
  3. UVA 11997 STL 优先队列
  4. DB天气app冲刺二阶段第十天
  5. ocp 1Z0-051 141-175题解析
  6. 常用AWK命令
  7. 日常使用 Git 的 19 个建议
  8. [LeetCode#12] Roman to Integer
  9. Git系列(1) Windows下Git服务器搭建
  10. UVA10534-----Wavio Sequence-----动态规划之LIS
  11. Hadoop基本概念
  12. Linux串口编程详解(转)
  13. Could not execute JDBC batch update; SQL [delete from role where roleId=?]; constraint [null]; neste
  14. 实际应用中遇到TimedRotatingFileHandler不滚动的问题
  15. Money King【题解】
  16. org.apache.commons.lang.exception.NestableRuntimeException
  17. dell T130服务器加内存
  18. MySQL系列详解七:MySQL双主架构演示-技术流ken
  19. java代码操作solr搜索引擎的增删查改
  20. mysql8.0CTE实现递归查询

热门文章

  1. codeforces346 Div.2 A.Round House
  2. c#比较两个List相等
  3. kafka的log存储解析——topic的分区partition分段segment以及索引等
  4. CentOS 6.5 Python Image Library 配置
  5. js截取中英文字符串、标点符号无乱码示例解读
  6. Easyui之datagrid实现点击单元格修改单元格背景颜色
  7. 【BZOJ】4144: [AMPPZ2014]Petrol
  8. jquery checked
  9. 原生js移动端touch事件实现上拉加载更多
  10. 运用TensorFlow处理简单的NLP问题