MEF学习
一、 什么是MEF
MEF(Managed Extensibility Framework)是一个用于创建可扩展的轻型应用程序的库。 应用程序开发人员可利用该库发现并使用扩展,而无需进行配置。 扩展开发人员还可以利用该库轻松地封装代码,避免生成脆弱的硬依赖项。 通过 MEF,不仅可以在应用程序内重用扩展,还可以在应用程序之间重用扩展。(摘自MSDN)
我的理解:应用/插件均使用约定好的协议(接口)进行开发。系统将自动扫描指定文件夹,并按协议自动导入。
二、 MEF简单例子
1、例子一
a、定义接口
public interface DemoOneInterface { void Send(string msg); }
b、使用接口
public class DemoOne { [Import] DemoOneInterface DO; public void Run() { DO.Send("DemoOne.Run"); } }
使用[Import]标记需要导入属性(DemoOneInterface DO;),如果不标记,则MEF不会进行导入。
c、创建插件类
[Export(typeof(DemoOneInterface))] public class DemoOneInherit1 : DemoOneInterface { #region DemoOneInterface Members public void Send(string msg) { Console.WriteLine("DemoOneInherit1 send {0}", msg); } #endregion }
插件类需要使用Export标记,并且声称导出类型。
d、查看效果
static void Main(string[] args) { new DemoOne().Run(); Console.ReadLine(); }
原来我们使用MEF,但并没有通知MEF去寻找插件。
我们对Main函数进行修改:
var demo = new DemoOne(); var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly)); //catalog.Catalogs.Add(new DirectoryCatalog("Addin")); //遍历运行目录下的Addin文件夹,查找所需的插件。 var _container = new CompositionContainer(catalog); _container.ComposeParts(demo); demo.Run();
修改后再次运行看看效果。
OK,运行起来了,和预期一样。
2、例子二
运行例子一,没有问题,但2个插件使用同一个的时候,会报错。
因此我们可以为Export加入别名(contractName),并且Import的时候也指定别名,MEF就会根据别名自动进行加载。
修改后代码如下:
public class DemoOne { [Import("2")] DemoOneInterface DO; public void Run() { DO.Send("DemoOne.Run"); } } public interface DemoOneInterface { void Send(string msg); } [Export("1",typeof(DemoOneInterface))] public class DemoOneInherit1 : DemoOneInterface { #region DemoOneInterface Members public void Send(string msg) { Console.WriteLine("DemoOneInherit1 send {0}", msg); } #endregion } [Export("2", typeof(DemoOneInterface))] public class DemoOneInherit12 : DemoOneInterface { #region DemoOneInterface Members public void Send(string msg) { Console.WriteLine("DemoOneInherit2 send {0}", msg); } #endregion }
运行效果:
3、例子三
有时我们希望一个同时使用多个插件,比如:输出log。
这时我们可以将Import改为ImportMany,并且修改Do的类型为IEnumerable<DemoOneInterface>来导入多个插件。
修改后代码:
public class DemoOne { [ImportMany] IEnumerable<DemoOneInterface> DoList; public void Run() { foreach (var _do in DoList) { _do.Send("DemoOne.Run"); } } } public interface DemoOneInterface { void Send(string msg); } [Export(typeof(DemoOneInterface))] public class DemoOneInherit1 : DemoOneInterface { #region DemoOneInterface Members public void Send(string msg) { Console.WriteLine("DemoOneInherit1 send {0}", msg); } #endregion } [Export(typeof(DemoOneInterface))] public class DemoOneInherit12 : DemoOneInterface { #region DemoOneInterface Members public void Send(string msg) { Console.WriteLine("DemoOneInherit2 send {0}", msg); } #endregion }
运行效果:
4、例子四
现在有很多插件使用同一个约定,但我想根据配置在同一个方法中调用某个插件。
这时我们需要使用ExportMetadata来为插件的特殊属性进行标记。
使用到Lazy,来进行延迟加载,并且获取插件标记的信息。(关于Lazy具体信息请自行查找)
a、新增插件描述类
public interface DemoOneInterfaceDepict { string Depict{get;} }
b、为插件定义描述
[Export(typeof(DemoOneInterface))] [ExportMetadata("Depict", "1")] public class DemoOneInherit1 : DemoOneInterface { #region DemoOneInterface Members public void Send(string msg) { Console.WriteLine("DemoOneInherit1 send {0}", msg); } #endregion } [Export(typeof(DemoOneInterface))] [ExportMetadata("Depict", "2")] public class DemoOneInherit12 : DemoOneInterface { #region DemoOneInterface Members public void Send(string msg) { Console.WriteLine("DemoOneInherit2 send {0}", msg); } #endregion }
c、修改DoList
IEnumerable<Lazy<DemoOneInterface,DemoOneInterfaceDepict>> DoList;
d、根据配置调用
public class DemoOne { [ImportMany] IEnumerable<Lazy<DemoOneInterface,DemoOneInterfaceDepict>> DoList; public void Run() { foreach (var _do in DoList.Where(item=>item.Metadata.Depict == ReadXml())) { _do.Value.Send("DemoOne.Run"); } } string ReadXml() { return "2"; } }
运行结果:
三、简化调用
上述4个例子运行正常,但我们一直没去在意Main函数里面的内容。
var demo = new DemoOne(); var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly)); //catalog.Catalogs.Add(new DirectoryCatalog("Addin")); //遍历运行目录下的Addin文件夹,查找所需的插件。 var _container = new CompositionContainer(catalog); _container.ComposeParts(demo); demo.Run();
看着头就晕了,难道每次构造一个函数,都这么写吗?那不是非常痛苦?!!!
重新设计一下:
1、使用基类
public abstract class BaseClass { public BaseClass() { var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly)); var _container = new CompositionContainer(catalog); _container.ComposeParts(this); } }
修改DemoOne类继承BaseClass
public class DemoOne : BaseClass
简化调用
var demo = new DemoOne(); demo.Run();
运行 ok。
2、使用扩展方法
每个类都要继承这个基类,由于C#只有单继承,已经继承了一个基类后,就比较麻烦。
因此衍生出第二种方法,新增扩展方法。
扩展方法
public static class ObjectExt { public static T ComposePartsSelf<T>(this T obj) where T : class { var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly)); catalog.Catalogs.Add(new DirectoryCatalog(".")); //catalog.Catalogs.Add(new DirectoryCatalog("addin")); var _container = new CompositionContainer(catalog); _container.ComposeParts(obj); return obj; } }
修改DemoOne类,新增构造函数,并且调用扩展方法
public class DemoOne { public DemoOne() { this.ComposePartsSelf(); } [ImportMany] IEnumerable<Lazy<DemoOneInterface,DemoOneInterfaceDepict>> DoList; public void Run() { foreach (var _do in DoList.Where(item=>item.Metadata.Depict == ReadXml())) { _do.Value.Send("DemoOne.Run"); } } string ReadXml() { return "2"; } }
简化调用
var demo = new DemoOne(); demo.Run();
运行 ok。