调试 MEF

在 Managed Extensibility Framework (MEF) 中调试问题可能非常困难,因为潜在问题与普通应用程序中的潜在问题不同。 本主题提供了特定于 MEF 的一些问题诊断方法,并且提供了这些问题的一些可能原因。

本主题包括下列各节。

  • 发现 MEF 问题
  • 检查可用部件
  • 导出/导入不匹配
  • 发现问题
  • 部件拒绝

发现 MEF 问题

解决 MEF 问题的第一步是在应用程序的 MEF 部分中定位问题。 下表列出了特定于 MEF 的问题:

问题

可能的原因

组合期间引发 ImportCardinalityMismatchException

由于部件缺失或被拒绝,无法使用匹配的导出填充导入。

- 或 -

预期单个导出的导入发现多个匹配项。

具有 ImportMany 特性的集合缺少预期内容。

预期部件缺失或被拒绝。

AllowDefault 特性设置为 true (AllowDefault=true) 的导入意外未填充。

预期的匹配项缺失或被拒绝。

检查可用部件

若要确定哪些部件可用于目录,可以在调试器中检查目录的 Parts 属性,在代码中循环访问该属性,或者使用 Mefx 组合分析工具。 有关 Mefx 的更多信息,请参见结构分析工具 (Mefx)。 具体而言,可以使用 mefx /importers 命令检查是否多个部件匹配给定的导入。 如果该导入不是集合,则此命令将生成异常。 如果导入不是集合,请通过使用 ImportMany 特性将导入更改为集合,或者移除部件之一。

如果希望看到的部件不存在,则表示未发现或者已拒绝这些部件。 有关更多信息,请参见发现问题。

如果可以看到一个部件,但是它与您希望它填充的导入不匹配,则表示出现了某种类型的不匹配。 有关更多信息,请参见下文的导出/导入不匹配。

导出/导入不匹配

为了使导出与特定导入匹配,必须满足以下所有条件。 如果未出现预期匹配,并且您已确认导出存在于目录中,请仔细检查这些条件。 手动构造协定以匹配特定导出时,这些条件也适用。 有关更多信息,请参见 GetExports 方法。

Property

匹配的条件

协定名称

必须完全匹配并且区分大小写。 如果协定名称是从类型推断得出的(例如,在没有任何参数的情况下应用 Export 特性时),则唯一可能的匹配项是从同一类型推断得出的另一名称。

协定类型

必须完全匹配。 不支持多态匹配。 即使提供了匹配的协定名称,协定类型也必须匹配。

必需的元数据

导入通过其元数据视图的属性要求的所有元数据必须由导出通过 ExportMetadata 特性或自定义元数据特性提供。 元数据键(元数据视图中属性的名称)和元数据值的类型必须匹配。

创建策略

导出和导入不能指定不同的创建策略。 有关更多信息,请参见 CreationPolicy

发现问题

如果部件未显示在目录中或者使用 Mefx 时未显示,则表示目录未发现该部件。 导致此故障的一些可能原因包括:

  • 该部件是抽象‏‎‏‏‏‏‎‎类型。 抽象类型不能用作部件。 使类型成为非抽象类型,或者创建非抽象子类型。

  • ImportingConstructor 特性缺失。 对于具有多个构造函数或者仅具有接受参数的构造函数的部件,必须指定一个构造函数,以便 MEF 使用 ImportingConstructor 特性。

  • 该部件具有 PartNotDiscoverable 特性。 此特性阻止部件被发现。

  • 该部件是开放式泛型类型。 MEF 不支持开放式泛型类型。 请使用类型的封闭式子类,或者导出单个属性。

使用 DirectoryCatalog 时可能出现其他故障:

  • 该部件是 EXE 文件。 默认 DirectoryCatalog 仅从 DLL 文件读取。 通过使用相应搜索模式创建,可以使用 DirectoryCatalog 从其他文件读取。

  • 部件的程序集具有缺失的引用。 使用的程序集不能从搜索路径(通常从它们自己的目录或者从全局程序集缓存)加载其引用。

  • 该部件的程序集面向其他 CPU 类型。 MEF 不会加载面向错误 CPU 类型的程序集。

部件拒绝

MEF 组合引擎保证部件绝不会以无效状态创建。 这称为“稳定组合”。 为了实现稳定组合,会在容器中隐藏具有未填充导入的部件。 这些部件称为“拒绝的部件”。

Mefx 对于诊断拒绝特别有用。 如果部件在 mefx /parts 命令中列出但是未显示在目录中,则表示部件已被拒绝。 还可以使用 mefx /rejected 命令获取已拒绝部件的列表。 有关 Mefx 的更多信息,请参见结构分析工具 (Mefx)

根据在导出/导入不匹配中讨论的规则,如果部件具有的导入无法由可用导出匹配,则拒绝该部件。 然而,事实通常是这样的:最先观察到的不可用部件被拒绝是因为它自己的导入的一个匹配项被拒绝。 该拒绝又可能由另一拒绝导致,依此类推。 这称为“级联拒绝”。 链中的最后一个拒绝称为“主拒绝”。 发现主拒绝是调试此类问题的主要难题。

例如,请考虑一个名为 ChainOne 的部件,它具有一个 ChainTwo 类型的导入。 ChainTwo 又导入 ChainThree,而 ChainThree 又导入 ChainFour。 以下代码声明这些部件:

    [Export]
    public class ChainOne
    {
        [Import]
        public ChainTwo chain { get; set; }
    }

   [Export]
    public class ChainTwo
    {
        [Import]
        public ChainThree chain { get; set; }
    }

    [Export]
    public class ChainThree
    {
        [Import]
        public ChainFour chain { get; set; }
    }

如果只有这些部件加载到容器中,则 ChainThree 将被拒绝,因为 ChainFour 不存在。 因为没有匹配的导出,所有无法填充它的导入。 这将导致 ChainTwo 被拒绝,然后又导致 ChainOne 被拒绝。 拒绝 ChainThree 是主拒绝。

发现主拒绝的最佳方式是使用 Mefx 工具。 mefx /causes 命令设计用于提供这种类型的分析并指导您发现导致问题的部件。 有关更多信息,请参见结构分析工具 (Mefx)