前言
Managed Extensibility Framework(MEF)是.NET平台下的一个扩展性管理框架,它是一系列特性的集合,包括依赖注入(DI)等。MEF为开发人员提供了一个工具,让我们可以轻松的对应用程序进行扩展并且对已有的代码产生最小的影响,开发人员在开发过程中根据功能要求定义一些扩展点,之后扩展人员就可以使用这些扩展点与应用程序交互;同时MEF让应用程序与扩展程序之间不产生直接的依赖,这样也允许在多个具有同样的扩展需求之间共享扩展程序。
MEF方式
MEF 提供一种通过“组合”隐式发现组件的方法。 MEF 组件(称为“部件-Part”)。部件以声明方式同时指定其依赖项(称为“导入-Import”)及其提供的功能(称为“导出-Export”)。
MEF原理上很简单,找出有共同接口的导入、导出。然后找到把导出的实例化,赋给导入。说到底MEF就是找到合适的类实例化,把它交给导入。
使用 MEF 编写的可扩展应用程序会声明一个可由扩展组件填充的导入,而且还可能会声明导出,以便向扩展公开应用程序服务。 每个扩展组件都会声明一个导出,而且还可能会声明导入。 通过这种方式,扩展组件本身是自动可扩展的。
如何声明一个部件-导入和导出
导出”是部件向容器中的其他部件提供的一个值,而“导入”是部件向要通过可用导出满足的容器提出的要求。 在特性化编程模型中,导入和导出是由修饰类或成员使用 Import 和Export 特性声明的。 Export 特性可修饰类、字段、属性或方法,而 Import 特性可修饰字段、属性或构造函数参数。为了使导入与导出匹配,导入和导出必须具有相同的协定。
假设有一个类HomeController,它声明了可以导入插件的类型是ITestRepository。
public class HomeController : Controller { [Import] public ITestRepository Repository { get; set; } }
这里有一个类,它声明为导出。类型同样为ITestRepository。
[Export(typeof(ITestRepository))] public class TestRepository:ITestRepository { public string GetTestString() { return "Hello World"; } }
这样我们使用Repository属性的时候就可以获得到TestRepository的实例。
如何导入多个部件
一般的 ImportAttribute 特性由一个且只由一个 ExportAttribute 填充。 如果有多个导出可用,则组合引擎将生成错误。若要创建一个可由任意数量的导出填充的导入,可以使用 ImportManyAttribute 特性。
一个接口
public interface ITestRepository { string GetTestString(); }
两个实现
[Export(typeof(ITestRepository))] public class TestRepository:ITestRepository { public string GetTestString() { return "Hello World"; } }
[Export(typeof(ITestRepository))] class TextRepository:ITestRepository { public string GetTestString() { return "Hello World Text"; } }
[ImportMany] public IEnumerable<ITestRepository> Repository { get; set; }
这样调用Repository就可以进行选择了
导入和导出的继承
如果某个类继承自部件,则该类也可能会成为部件。 导入始终由子类继承。 因此,部件的子类将始终为部件,并具有与其父类相同的导入。通过使用 Export 特性的声明的导出不会由子类继承。 但是,部件可通过使用 InheritedExport 特性继承自身。 部件的子类将继承并提供相同的导出,其中包括协定名称和协定类型。 与 Export 特性不同,InheritedExport 只能在类级别(而不是成员级别)应用。 因此,成员级别导出永远不能被继承。
下面四个类演示了导入和导出继承的原则。 NumTwo 继承自 NumOne,因此 NumTwo 将导入 IMyData。 普通导出不会被继承,因此 NumTwo 将不会导出任何内容。 NumFour 继承自NumThree。 由于 NumThree 使用了 InheritedExport,因此 NumFour 具有一个协定类型为 NumThree 的导出。 成员级别导出从不会被继承,因此不会导出 IMyData。
[Export] public class NumOne { [Import] public IMyData MyData { get; set; } } public class NumTwo : NumOne { //导入会被继承,所以NumTwo会有导入属性 IMyData //原始的导出不能被继承,所以NumTwo不会有任何导出。所以它不会被目录发现 } [InheritedExport] public class NumThree { [Export] Public IMyData MyData { get; set; } //这个部件提供两个导出,一个是NumThree,一个是IMyData类型的MyData } public class NumFour : NumThree { //因为NumThree使用了InheritedExport特性,这个部件有一个导出NumThree。 //成员级别的导出永远不会被继承,所以IMydata永远不是导出 }
创建策略
当部件指定执行导入和组合时,组合容器将尝试查找匹配的导出。 如果它将导入与导出成功匹配,则导入成员将设置为导出的对象的实例。 导出部件的创建策略控制此实例来源于何处。导入和导出都可从值 Shared、NonShared 或 Any 中指定部件的创建策略。 导入和导出的默认值均为 Any。
例如:
[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
声明周期和释放
由于部件承载于组合容器中,因此其生命周期可能比普通对象更复杂。需要在关闭时执行工作的部件和需要释放资源的部件应照常为 .NET Framework 对象实现 IDisposable。 但是,由于容器创建并维护对部件的引用,因此只有拥有部件的容器才应对其调用 Dispose 方法。 容器本身实现 IDisposable,并且作为 Dispose 中其清理的一部分,它将对拥有的所有部件调用 Dispose。 因此,当不再需要组合容器及其拥有的任何部件时,您应始终释放该组合容器。
对于生存期很长的组合容器,创建策略为“非共享”的部件的内存消耗可能会成为问题。 这些非共享部件可以多次创建,并且在容器本身被释放之前将不会得到释放。 为了应对这种情况,容器提供了 ReleaseExport 方法。 如果对非共享导出调用此方法,将会从组合容器中移除该导出并将其释放。 仅由移除的导出使用的部件以及树中更深层的诸如此类部件将也会被移除并得到释放。 通过这种方式,不必释放组合窗口本身即可回收资源。