MEF的学习笔记
为什么要使用MEF
在商业应用软件开发过程中,对于各个软件项目,都需要建立相应的系统框架,为了更好的规范系统的开发,提高生产效率,应该在公司级别制定相应的API标准。这些API标准将站在系统架构层次,以同样一个核心框架构建出不同的商业应用。
对于各个商业应用中存在花样繁多的需求,同时又存在一些公用的模块,为了将这些可变的和相对稳定的功能模块有机的整合在一个系统框架下,那么就需要实现系统框架的可自定义插件开发。目前在MEF之前,业界也在大量的使用如 Castle Windsor、Structure Map、Spring.Net 以及Unity等依赖注入方式实现插件开发。而这些体系在.net平台中应用案例较少,在目前公司来说,基本还是空白,因此选择MEF这样一个全新的技术方案,相对其他方案门槛较低。
MEF概念的理解
可组合的部件(或简称“Part”):一个部件可以向其他部件提供服务,也可以使用其他部件提供的服务,它可以存在任何位置,可以是Web服务,外部系统,本系统。
导出:导出是服务提供者
导入:导入是服务使用者
约定:服务提供者与使用者之间使用的标示符,类似于身份识别。
组合:对部件实例化,建立组合关系,是的导出部件和导入部件相匹配。
MEF的工作原理
MEF的核心包括一个catalog(目录)和一个CompositionContainer(组合容器)。category用于发现扩展,而container用于协调创建和梳理依赖性。每个可组合的Part提供了一个或多个Export(导出),并且通常依赖于一个或多个外部提供的服务或 Import(导入)。
MEF的Demo
demo1:宿主mef ,学习compose的过程以及部件基本的特性标记
1.定义服务接口
interface IGetString { void WriteString(); }
2.定义服务的实现
[Export(typeof(IGetString))] class GetString :IGetString { public void WriteString() { Console.WriteLine("Hello Mef demo1!"); } }
3.配置宿主程序
class Program { /// <summary> /// 导入部件 /// </summary> [Import(typeof(IGetString))] public IGetString Service { get; set; } //组合部件 void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); p.Service.WriteString(); Console.ReadLine(); } }
4.运行效果
demo2:多个部件的组合,学习ImportMany
当同一个接口有多个实现的时候,MEF提供了ImportMany的方式,将实现多个
[Export(typeof(IGetString))] class GetString1 :IGetString { public void WriteString() { Console.WriteLine("Hello string1!"); } } [Export(typeof(IGetString))] class GetString2 : IGetString { public void WriteString() { Console.WriteLine("Hello string2!"); } }
宿主程序代码:
class Program { /// <summary> /// 导入 /// </summary> [ImportMany(typeof(IGetString))] public IEnumerable<IGetString> Service { get; set; } /// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); foreach (var server in p.Service) server.WriteString(); Console.ReadLine(); } }
运行效果:
demo3:多个部件和契约的配合
对export添加字符串标示信息
[Export("txt",typeof(IGetString))] class GetString :IGetString { public void WriteString() { Console.WriteLine("Hello string1!"); } } [Export("db",typeof(IGetString))] class GetString2 : IGetString { public void WriteString() { Console.WriteLine("Hello string2!"); } }
宿主程序导入部分同样增加字符串信息,与导出部件保持一致
class Program { /// <summary> /// 导入 /// </summary> [Import("db",typeof(IGetString))] public IGetString Service { get; set; } /// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); p.Service.WriteString(); Console.ReadLine(); } }
运行效果:
demo4:Import和Export的应用场景
在MEF中,导入和导出可以应用在类,字段,属性,方法,并允许多个部件同时实现一个接口,和继承的特性。
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes")] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class ExportAttribute : Attribute { //...... }
导出属性、字段或方法
class GetString { /// <summary> /// 导出属性 /// </summary> [Export("txt")] public string GetString1 { get { return "this is a fileds!"; } } [Export(typeof(Action<string>))] public void GetString2(string name) { Console.WriteLine(name); } }
导入属性、字段或方法
///// <summary> ///// 导入 ///// </summary> [Import("txt")] public string WriteString1 { get; set; } [Import(typeof(Action<string>))] public Action<string> WriteString2 { get; set; }
宿主程序
/// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); Console.WriteLine(p.WriteString1); p.WriteString2("this is a parameter"); Console.ReadLine(); }
输出结果:
demo5:组合部件的嵌套
在导出部件中进行了导入操作
/// <summary> /// 服务接口 /// </summary> interface IGetString { void WriteString(); } /// <summary> ///导出部件 /// </summary> [Export("txt",typeof(IGetString))] class GetString1 :IGetString { public void WriteString() { Console.WriteLine("Hello string1!"); } } /// <summary> /// 导出部件 /// </summary> [Export("db",typeof(IGetString))] class GetString2 : IGetString { public void WriteString() { Console.WriteLine("Hello string2!"); } } /// <summary> /// 导出部件导入了其他部件 /// </summary> [Export] class Getstring { [Import("txt",typeof(IGetString))] public IGetString Txt { get; set; } [Import("db", typeof(IGetString))] public IGetString Db { get; set; } }
宿主程序:
/// <summary> /// 导入 /// </summary> [Import] public Getstring Service { get; set; } /// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); //根据最近一层的服务提供输出 p.Service.Txt.WriteString(); p.Service.Db.WriteString(); Console.ReadLine(); }
测试结果:
demo6:组合部件的延迟加载
部件准备工作
/// <summary> /// 服务接口 /// </summary> interface IGetString { void WriteString(); } /// <summary> ///导出部件 /// </summary> [Export("txt",typeof(IGetString))] class GetString1 :IGetString { private string initTime; public GetString1() { initTime = DateTime.Now.ToString("hh:mm:ss:"); } public void WriteString() { Console.WriteLine("部件1初始化时间:\"{0}\"",initTime); } } /// <summary> /// 导出部件 /// </summary> [Export("db",typeof(IGetString))] class GetString2 : IGetString { private string initTime; public GetString2() { initTime = DateTime.Now.ToString("hh:mm:ss:"); } public void WriteString() { Console.WriteLine("部件2初始化时间:\"{0}\"", initTime); } }
导入
/// <summary> /// 导入 /// </summary> [Import("txt",typeof(IGetString))] public IGetString Service1 { get; set; } /// <summary> /// 导出 /// </summary> [Import("db", typeof(IGetString))] public Lazy<IGetString> Service2 { get; set; }
宿主程序:
static void Main() { Program p = new Program(); //组合部件工作 p.Compose(); System.Threading.Thread.Sleep(2000); p.Service1.WriteString(); //通过延迟加载,时间间隔为2秒 p.Service2.Value.WriteString(); Console.ReadLine(); }
输出效果:
demo7:组合部件的继承
导出部件,在接口中使用InheritedExport特性,在实现类中将省略Export标记
/// <summary> /// 继承导出特性 /// </summary> [InheritedExport] interface IGetString { void WriteString(); } /// <summary> ///继承导出功能 /// </summary> class GetString :IGetString { public void WriteString() { Console.WriteLine("这是继承的导出部件"); } }
宿主程序:
/// <summary> /// 导入 /// </summary> [Import] public IGetString Service { get; set; } /// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); //组合部件工作 p.Compose(); p.Service.WriteString(); Console.ReadLine(); }
输出效果:
组合容器(CompositionContainer)和目录(Catalog)
经过前面的demo练习,我们已经了解了MEF中的导入(Import)和导出(Export)。在本系列的第一篇文章中我们知道MEF其实还包括另外两个核心内容:组合容器(CompositionContainer)和目录(Catalog)。
在宿主程序中,我们需要通过组合容器和目录,将部件功能引入到当前的宿主程序应用中。
/// <summary> /// 组合 /// </summary> void Compose() { var catelog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catelog); container.ComposeParts(this); }
组合容器:比较常用的有CompositionContainer,而有时候会需要用到CompositionBatch,这里讲不做讲解。
目录:Assembly Catalog(程序集目录),Directory Catalog,Aggregate Catalog,Type Catalog,和仅使用在Silverlight中得目录Deployment Catalog( Silverlight only),Filtered Catalog.其中将着重讲解Assembly Catalog(程序集目录),Directory Catalog,Aggregate Catalog。
1.Assembly Catalog
可以在给定的Assembly 发现所有的导出部件,使用类型AssemblyCatalog。
2.Directory Catalog
它可以在给定的目录(路径,相对路径或绝对路径)中发现导出部件,使用类型DirectoryCatalog。如果你使用的是相对路径,则相对的是当前AppDoamin的基路径。DirectoryCatalog只会对给定目录进行一次性扫描,目录发生变化是容器不会主动刷新,如果需要刷新给定的目录需要调用方法:Refresh() ,当目录刷新时,容器也会重新组合部件。这个通常将一些动态链接库(.dll)作为部件导入到宿主程序中,可以灵活将DirectoryCatalog中的dll增加或移除,以对同一个API实现不同的应用。
demo8:使用Directory Catalog
在项目中开发导出部件:通过添加类库的方式,生成相应功能的.dll文件,其中需要遵照一个API标准(IGetString)。
1.通过MEFDemo8Service,定义API:
/// <summary> /// 定义API,同时定义为继承导出方式 /// </summary> [InheritedExport] public interface IGetString { void OutPut(); }
2.通过MEFDemoPart1和Part2分别实现相应的接口
分别实现了部件1和部件2的方法
/// <summary> /// 实现接口 /// </summary> public class GetString:MEFDemo8Service.IGetString { public void OutPut() { Console.WriteLine("执行了部件1的方法"); } }
3.在宿主程序中,添加API的引用以及添加分别生成part1和part2的.dll文件
其中Lib文件夹中的dll将属性设置为内容,并复制
在宿主程序中,通过导入,组合(使用DirectoryCatalog,指定Lib文件夹),再通过Main的执行,显示出具体的实现内容,代码如下:
class Program { /// <summary> /// 导入部件 /// </summary> [ImportMany(typeof(MEFDemo8Service.IGetString))] public IEnumerable<MEFDemo8Service.IGetString> Service { get; set; } //组合部件 void Compose() { var catelog = new DirectoryCatalog("Lib"); var container = new CompositionContainer(catelog); container.ComposeParts(this); } static void Main() { Program p = new Program(); p.Compose(); foreach (var server in p.Service) server.OutPut(); Console.ReadLine(); }
最终的执行效果:
当在Lib中移出了部件1
最终效果:
当部件添加到Lib中,又得到了最开始的效果。
由此可见,使用Lib,可以将我们的具体实现通过物理方式隔离,在需要的时候添加,不需要的时候移除即可。
3.Aggregate Catalog
聚集目录,有时候我们使用单一的Assembly Catalog和Directory Catalog并不能解决我们的需求,我们可能需要同时使用到他们,这时候我们便可使用Aggregate Catalog,我们可以将Assembly Catalog和Directory Catalog同时添加到目录中,这种添加可以通过构造函数实现,也可以通过目录集合的添加方法来实现:catalog.Catalogs.Add(...)。聚集目录使用类型AggregateCatalog。
MEF带来的联想
留给大家来回答吧!!!
,
MEF的学习笔记的更多相关文章
- C#可扩展编程之MEF学习笔记(五):MEF高级进阶
好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用 ...
- C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻
前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...
- C#可扩展编程之MEF学习笔记(三):导出类的方法和属性
前面说完了导入和导出的几种方法,如果大家细心的话会注意到前面我们导出的都是类,那么方法和属性能不能导出呢???答案是肯定的,下面就来说下MEF是如何导出方法和属性的. 还是前面的代码,第二篇中已经提供 ...
- C#可扩展编程之MEF学习笔记(二):MEF的导出(Export)和导入(Import)
上一篇学习完了MEF的基础知识,编写了一个简单的DEMO,接下来接着上篇的内容继续学习,如果没有看过上一篇的内容, 请阅读:http://www.cnblogs.com/yunfeifei/p/392 ...
- C#可扩展编程之MEF学习笔记(一):MEF简介及简单的Demo
在文章开始之前,首先简单介绍一下什么是MEF,MEF,全称Managed Extensibility Framework(托管可扩展框架).单从名字我们不难发现:MEF是专门致力于解决扩展性问题的框架 ...
- Caliburn.Micro学习笔记(一)----引导类和命名匹配规则
Caliburn.Micro学习笔记目录 用了几天时间看了一下开源框架Caliburn.Micro 这是他源码的地址http://caliburnmicro.codeplex.com/ 文档也写的很详 ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- PHP-自定义模板-学习笔记
1. 开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2. 整体架构图 ...
- PHP-会员登录与注册例子解析-学习笔记
1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...
随机推荐
- ubuntu vim8.0源码安装
安装篇 从https://github.com/vim/vim下载相应zip源码文件,利用unzip vim-master.zip 命令解压到当前用户目录,即~: 解压后进入vim的src目录,首先, ...
- wireshark常用过滤条件
抓取指定IP地址的数据流: 如果你的抓包环境下有很多主机正在通讯,可以考虑使用所观察主机的IP地址来进行过滤.以下为IP地址抓包过滤示例: host 10.3.1.1:抓取发到/来自10.3.1.1的 ...
- spine实现预加载(一)
前言 本文实现了spine动画的预加载,解决在战斗等大量加载spine动画的时候出现卡顿现象. 这里使用和修改三个类,直接修改的源码,当然你也可以继承LuaSkeletonAnimation,自己封装 ...
- Java注解和代理实现
1.定义注解 import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java. ...
- cf 710 E Generate a String
题意: 开始你有数字$0$,你可以用代价$x$将该数字加$1$或减$1$(当$x > 0$时),或用代价$y$将该数字变为$2x$,那么问得到数字$n$所需的最少代价是多少. 数据范围$1 \l ...
- Android Studio 自定义debug签名文件keystore
Android Studio 自定义debug签名文件keystore
- WebForm Application Viewstate 以及分页(功能性的知识点)
Application: 全局公共变量组 存放位置:服务器 特点:所有访问用户都是访问同一个变量,但只要服务器不停机,变量一直存在于服务器的内存中,不要使用循环大量的创建Application对象,可 ...
- 一看便知spring+quartz定时任务
这是我经过网上收集然后加上自己的测试写的,以便大家使用 标配:已测 注意需要的包:(在已经配置spring 的情况下) quartz-all-1.6.jar spring-context ...
- SQL知识整理三:变量、全局变量、视图、事务、异常
变量 1.局部变量的声明(一个@) declare @n int --声明变量关键字为declare 然后@加变量名 后面是变量类型 declare @s varchar(36) 2 ...
- PostGIS导入导出SHP文件常用命令
SHP导入POSTGIS数据库 引用 直接导入数据库 shp2pgsql -I -s 2437 -W GBK shop_point.shp public.ntable | psql -U postg ...