元数据存储区

更新:2007 年 11 月

Visual Studio Windows Presentation Foundation (WPF) 设计器框架可用来将设计时元数据与实现分离。将元数据与运行时代码分离是一个重要的设计原则,原因如下。

  • 一遍一遍地生成以及团队之间的集成逻辑可能会使将元数据编译成框架代码变得异常繁琐。

  • 将元数据编译为运行时代码可防止外部工具(如 WPF 设计器或 Expression Blend)在以后修改该元数据。这是一个重要的灵活性问题。如果不将设计时元数据与代码分离,那么,没有 .NET Framework 的新版本,Visual Studio 就无法确定其设计器的版本。

  • 将元数据编译为运行时会大大增加运行时程序集的大小。设计时属性还会减慢运行时的执行速度。在向内存中加载其他属性时,那些使用反射的运行时功能(如数据绑定)会受到影响。

  • 设计时元数据提供设计器的“个性”。设计器的功能大部分绑定到用来承载它的应用程序(而不是运行时)。WPF 设计器和 Expression Blend 使用不同的元数据集来提供面向特定用户类型的功能集。

元数据存储区

元数据存储区是设计时元数据的存储位置。元数据存储区的 API 非常简单。可以通过调用 AddAttributeTable 方法来添加元数据属性表。在将某个表添加到元数据存储区中时,在该表中定义的属性将变为可通过 TypeDescriptor 查询使用。如果已经对某个类型进行了查询,而且该表中包含此类型的其他属性,则会引发一个 Refreshed 事件,以便报告此类型的元数据已经发生更改。

属性表

属性表实际上是一个只读字典,但其键和值分别进行计算。对于特定类型的属性,查询属性表将非常高效。实际的属性集是按需创建的。可以通过调用 GetCustomAttributes 方法来检索特定类型的自定义元数据。

属性 (Attribute) 表仅支持类型的属性 (Property),而不支持字段或方法的属性 (Attribute)。

属性表生成器

若要创建属性表,需要先创建 AttributeTableBuilder 类的一个实例。通过调用 AddCustomAttributes 重载将元数据添加到属性表生成器中。添加完元数据后,通过调用 CreateTable 方法从属性表生成器中生成一个属性表。属性表生成器方法支持回调委托,因此可将属性表的创建一直推迟到需要时。

创建自定义属性

元数据存储区依赖以下事实:已经为自定义属性 (Attribute) 的 TypeId 属性 (Property) 正确定义了重写。元数据存储区使用 TypeId 属性 (Property) 来确定两个相同或不同类型的属性 (Attribute) 是否应当被视为相同的实例。

Attribute 基类按如下方式定义 TypeId 属性。

    public class Attribute
    {
        ...

        public virtual object TypeId
        {
            get
            {
                return base.GetType();
            }
        }

        ...
    }

此实现使得具有相同 Attribute 类型的两个实例显示为同一个属性。其中的一个实例将被默认 TypeDescriptor 实现忽略。如果这不是自定义属性 (Attribute) 所需的行为(正如对 FeatureAttribute 类一样),则该自定义属性 (Attribute) 必须重写 TypeId 属性 (Property),以便针对每个类型实例返回一个唯一的对象。例如,FeatureAttribute 类使用下面的代码重写 TypeId 属性。

public override object TypeId
{
    get { return this; }
}

由于 this 表示每个对象实例都对应一个唯一的对象,因此在与元数据存储区结合使用时,FeatureAttribute 可以安全地多次修饰同一个类并生成所需结果。

元数据程序集的命名约定

设计时代码在特定元数据程序集中部署。受所有设计器支持的设计时功能在主库名称附加有“.Design”的程序集内部署。仅受 Visual Studio 支持的设计时功能在主库名称附加有“.VisualStudio.Design”的程序集内部署。下表显示了名为 CustomControlLibrary.dll 的运行时控件库的示例名称。

设计器

设计时程序集名称

仅限 Visual Studio

CustomControlLibrary.VisualStudio.Design.dll

仅限 Expression Blend

CustomControlLibrary.Expression.Design.dll

所有设计器

CustomControlLibrary.Design.dll

加载元数据程序集

当设计器加载某个运行时程序集时,它还会搜索相应的元数据程序集。如果找到了相应的元数据程序集,则会在加载该运行时程序集之后立即加载这些元数据程序集。

向项目添加新的程序集引用时,将搜索所有相应的元数据程序集。如果找到,将加载它们。

在重新生成元数据程序集时,设计器会重新加载它们。

说明:

*.Design.dll 元数据程序集会先于设计器特定的 *.VisualStudio.Design.dll 和 *.Expression.Design.dll 程序集加载。设计器特定的元数据会重写共享的元数据。

元数据程序集的搜索顺序

下面的搜索顺序适用于由项目直接引用的程序集。

  1. 设计器搜索引用的运行时程序集所在的文件夹。若要查找此位置,可以利用生成功能查找程序集时使用的算法,该算法包括对 SDK 文件夹和其他路径的搜索。

  2. 设计器在控件的运行时程序集所在的文件夹中搜索“Design”子文件夹。

虽然控件的运行时程序集可以从全局程序集缓存 (GAC) 之外加载,但引用总是指向 GAC 外部的位置。此位置通常位于 SDK 文件夹中。WPF 设计器使用 Visual Studio API 在文件系统上查找引用的程序集,即使未指定项目 HintPath 也是如此。设计器将尝试从引用控件运行时程序集的位置(而不是加载它的位置)加载元数据程序集。

将加载间接引用的程序集,因为它们是从项目所引用的程序集引用的。例如,如果项目有对程序集 MyAssembly 的引用,而 MyAssembly 又有对 MyOtherAssembly 的引用,由于该项目不直接引用 MyOtherAssembly,因此将 MyOtherAssembly 视为是间接引用的。

在这种情况下,程序集不是生成功能需要的程序集,生成系统不在文件系统中查找间接引用的程序集所在的位置。下表显示了设计器如何加载间接引用的程序集。

引用的程序集

搜索过程

从 GAC 加载的文件

在 SDK 文件夹中搜索相应的元数据程序集。如果找到了此程序集,则将在它的路径和“Design”子文件夹中搜索任何相应的元数据程序集。

从 GAC 外部的位置加载的文件

在运行时程序集的路径及其“Design”子文件夹中搜索相应的元数据程序集。

查找 IRegisterMetadata 实现

元数据程序集必须包含 IRegisterMetadata 接口的一个或多个实现。可使用反射功能来查找 IRegisterMetadata 实现。如果一个程序集内存在多个 IRegisterMetadata 实现,则按照反射 API 所返回的顺序实例化和调用每个实现。

请参见

参考

Microsoft.Windows.Design.Metadata

MetadataStore

AttributeTable

AttributeTableBuilder

FeatureAttribute

其他资源

WPF 设计器扩展性