MEF框架学习之旅(六)元数据和元数据视图
为了允许访问元数据,MEF 使用 .NET Framework 4 的一个新 API,即 System.Lazy<T>。使用该 API 可延迟实例的实例化,直至访问 Lazy 的 Value 属性。MEF 使用 Lazy<T,TMetadata> 进一步扩展 Lazy<T>,以允许在不实例化基础导出的情况下访问导出元数据。
元数据可用于将导出的对象的属性传递到导入部件。 导入部件可以使用此数据来决定要使用哪些导出,或收集有关导出的信息而不必构造导出。 因此,导入必须为延迟导入才能使用元数据。
为了使用元数据,您通常会声明一个称为“元数据视图”的接口,该接口声明什么元数据将可用。 元数据视图接口必须只有属性,并且这些属性必须具有 get 访问器。访问元数据属性时,MEF 将动态实现 TMetadata,且将基于导出提供的元数据来设置值。
注意:
1.如果接口/继承类中有字段或属性,则在导出具体实例时需要为每一个属性或字段赋值;或者将DefaultValue特性标注在需要的字段或属性上。
2.接口中的属性只能get不能set
下面的接口是一个示例元数据视图。
代码段
public interface IPluginMetadata { string Name { get; } [DefaultValue(1)] int Version { get; } }
也可以使用泛型集合 IDictionary<string, object> 作为元数据视图,但这样将会丧失类型检查的优点,因此应避免这样做。
通常,在元数据视图中命名的所有属性都是必需的,并且不会将未提供这些属性的任何导出视为匹配。 DefaultValue 特性指定属性是可选的。 如果未包括属性,则将为其分配指定为 DefaultValue 的参数的默认值。 下面是用元数据修饰的两个不同的类。 这两个类都将与前面的元数据视图匹配。
代码段
[Export(typeof(IPlugin)), ExportMetadata("Name", "Logger"), ExportMetadata("Version", 4)] public class Logger : IPlugin { } [Export(typeof(IPlugin)), ExportMetadata("Name", "Disk Writer")] //Version is not required because of the DefaultValue public class DWriter : IPlugin { }
元数据是通过使用 ExportMetadata 特性在 Export 特性之后表示的。 每一段元数据都由一个名称/值对组成。 元数据的名称部分必须与元数据视图中相应属性的名称匹配,并且值将分配给该属性。
导入程序负责指定将使用的元数据视图(如果有)。 包含元数据的导入将声明为延迟导入,其元数据接口作为 Lazy<T,T> 的第二个类型参数。 下面的类导入前面的部件以及元数据。
代码段
public class Addin { [Import] public Lazy<IPlugin, IPluginMetadata> plugin; }
在许多情况下,您需要将元数据与 ImportMany 特性结合,以便分析各个可用的导入并选择仅实例化一个导入,或者筛选集合以匹配特定条件。 下面的类仅实例化具有 Name 值“Logger”的 IPlugin 对象。
代码段
public class User { [ImportMany] public IEnumerable<Lazy<IPlugin, IPluginMetadata>> plugins; public IPlugin InstantiateLogger () { IPlugin logger = null; foreach (Lazy<IPlugin, IPluginMetadata> plugin in plugins) { if (plugin.Metadata.Name = "Logger") logger = plugin.Value; } return logger; } }
使用 ExportMetadata 特性可提供很大的灵活性,但是使用该特性时需要注意一些事项:
在 IDE 中无法发现元数据键。部件编写者必须知道对导出有效的元数据键和类型。
编译器不会验证元数据以确保其正确。
ExportMetadata 会向代码添加更多干扰信息,从而隐藏真正意图。
MEF 提供了解决方案来解决以上的问题:自定义导出。
MEF 允许创建包括其自己的元数据的自定义导出。创建自定义导出包括创建还指定元数据的派生 ExportAttribute。
下面的类定义一个自定义特性。
代码段
[MetadataAttribute] [AttributeUsage(AttributeTargets.Class, AllowMultiple=true)] public class MyAttribute : ExportAttribute { public MyAttribute(string myMetadata) : base(typeof(IMyAddin)) { MyMetadata = myMetadata; } public string MyMetadata { get; private set; } }
此类定义一个名为 MyAttribute 的自定义特性,具有协定类型 IMyData 和某些名为 MyMetadata 的元数据。 用 MetadataAttribute 特性标记的类中的所有属性都将被视为自定义特性中定义的元数据。 下面两个声明等效。
代码段
[Export(typeof(IMyAddin), ExportMetadata("MyMetadata", "theData")] public MyAddin myAddin { get; set; } [MyAttribute("theData")] public MyAddin myAddin { get; set; }
在第一个声明中,协定类型和元数据是显式定义的。 在第二个声明中,协定类型和元数据在自定义特性中是隐式的。 特别是,在必须将大量的相同元数据(例如,作者或版权信息)应用于多个部件的情况下,使用自定义特性可以节约大量的时间和重复工作。 此外,可以创建自定义特性的继承树来为变体留出余地。
若要在自定义特性中创建可选元数据,您可以使用 DefaultValue 特性。 如果此特性应用于自定义特性类中的属性,它将指定修饰的属性是可选的,并且不必由导出程序提供。 如果未提供属性的值,则将为属性分配其属性类型的默认值(通常为 null、false 或 0。)
再来看一个示例:
代码段
public interface IClassMetadata { string ClassName { get; } string OtherInfo { get; } } [MetadataAttribute] [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] public class ExportStudent : ExportAttribute, IClassMetadata { public ExportStudent() : base() { } public ExportStudent(string contractName) : base(contractName) { } public ExportStudent(Type contractType) : base(contractType) { } public ExportStudent(string contractName, Type contractType) : base(contractName, contractType) { } public string ClassName { get; set; } //可选时必须加上 [DefaultValue("")] public string OtherInfo { get; set; } } [ExportStudent(ClassName = "一年级三班)] public class Student { public string Name { get; set; } public int Age { get; set; } }
ExportStudent使用 MetadataAttribute 进行修饰,这指定该特性提供元数据。此特性告知 MEF 查看所有公共属性,并通过将属性名称用作键,对导出创建相关联的元数据。在这种情况下,唯一的元数据为ClassName、OtherInfo。在这个实例中AttributeUsage属性仅对类有效,且只能存在一个 ExportStudent特性,一般来说,AllowMultiple 应设置为 false;如果为 true,则导入程序将传递一组值而不是单个值。当多个导出具有同一成员的同一约定的不同元数据时,AllowMultiple 应保留为 True。
如您所见,自定义导出可确保为特定导出提供正确的元数据。这些导出还可减少代码中的干扰信息,更加容易发现,并且可通过特定于域来更好地表达意图。
相关阅读: