【问题标题】:Can anyone explain to me why the following code throws System.Reflection.AmbiguousMatchException?谁能向我解释为什么以下代码会引发 System.Reflection.AmbiguousMatchException?
【发布时间】:2019-02-05 13:58:12
【问题描述】:
using System;
using System.Reflection;

namespace A
{
  interface IObjectWithId<TId>
  {
    TId Id { get; }
  }
  interface IEntityBase : IObjectWithId<object>
  {
    new object Id { get; }
  }
  abstract class BusinessObject<TId> : IObjectWithId<TId>
  {
    public abstract TId Id { get; }
  }
  class EntityBase : BusinessObject<object>, IEntityBase
  {
    public override object Id { get { return null; } }
  }

  public static class Program
  {
    public static void Main()
    {
      Console.WriteLine(typeof(EntityBase).GetProperty("Id", BindingFlags.Instance | BindingFlags.Public));
    }
  }
}

我得到了这个:

System.Reflection.AmbiguousMatchException was unhandled
  Message="Ambiguous match found."
  Source="mscorlib"
  StackTrace:
       at System.RuntimeType.GetPropertyImpl(String name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
       at System.Type.GetProperty(String name, BindingFlags bindingAttr)
       at A.Program.Main() in C:\Home\work\A\Program.cs:line 26
  InnerException: 

Microsoft Visual Studio 2008
版本 9.0.30729.1 SP
Microsoft .NET 框架
版本 3.5 SP1

编辑:

奇怪的是,看起来其他人无法复制它。虽然它每次都会在我的机器上崩溃。我发现这段代码:

Console.WriteLine(typeof(EntityBase).GetProperty("Id", BindingFlags.Instance | BindingFlags.Public, null, typeof(object), Type.EmptyTypes, null));

工作正常,虽然应该是一样的。

【问题讨论】:

  • 没有提供更多信息的异常细节吗?
  • 我刚刚在 VS2008/.net 3.5 中运行了这段代码,没有遇到异常。您使用的是哪个版本的 .net?
  • 我也不例外。 VS2008/.Net 3.5
  • 这不是我发明的,伙计们。这真的发生在我身上。
  • 我删除了关于信息,因为它在这里无关紧要。

标签: .net reflection


【解决方案1】:

为了回答您的问题,我会让您熟悉术语“方法表”。这是 .NET 框架中类型的内部表示的一部分,其中每个 .NET 类型都有自己的方法表。您可以将其想象为包含该类型的所有方法和属性的哈希映射(或字典)。关键是方法/属性签名(方法名称和参数类型,没有返回类型),值是匹配方法/属性的集合,以及一些反射元数据信息,例如哪个类型声明了方法/属性。

当类A派生自基类B,或实现接口C时,BC的方法表中的项目直接在@987654326的方法表中可用@。如果A 的方法表已经包含具有特定签名的项目,则该项目将添加到相同签名的集合中,因此现在A 将具有签名指向的2 个方法/属性。区分这些重复条目的唯一方法是比较描述签名所代表声明的类型的元数据。

让我们使用接口IObjectWithId&lt;TId&gt;,它定义了一个属性TId ID { get; set; }EntityBase 类实现了IObjectWithId&lt;TId&gt;,因此它的方法表接收到一个TId ID { get; set; } 属性。同时这个类实现了IEntityBase 接口,这给了它Object ID { get; set; } 属性。 EntityBase 类然后接收相同签名下的两个属性(因为返回类型不参与签名),同时它仍然会暴露 2 个不同的属性。以下声明将导致编译错误:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        public int ID { get; set; }
   }

因为IEntityBase 没有实现。同样,下面的也会失败:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        public object ID { get; set; }
   }

因为这次IObjectWithId&lt;int&gt;不满意。您可以尝试这样做:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        public object ID { get; set; }
        public int ID { get; set; }
   }

只是因为有 2 个具有相同签名的属性而收到另一个编译错误。

解决此问题的方法是显式实现至少一个冲突签名:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        private object objID;
        private int intID;

        object IEntityBase.ID { get { return objID; } set { objID = value; } }     
        int IObjectWithId<int>.ID { get { return intID; } set { intID = value; } }
   }

现在,回到您的代码 - 您使用 object 而不是 TId,这创建了一个罕见但有趣的案例 - 两个 ID 属性 unify 因为它们的签名相同。所以这个类:

   public class EntityBase : IEntityBase, IObjectWithId<object>
   {
        public object ID { get; set; }
   }

将编译,因为ID 属性满足两个接口。但是,EntityBase 类的方法表中仍然有 两个 ID 属性(每个接口都有一个)。这两个属性由编译器自动分配给EntityBase 类中的相同实现(该过程称为unification)。

以下代码:

typeof(EntityBase).GetProperty(
    "ID", BindingFlags.Instance | BindingFlags.Public);

将查看EntityBase 类的方法表,将看到该签名的两个属性条目,但不知道该选择哪一个。

这是因为你可能已经这样实现了你的类:

   public class EntityBase : IEntityBase, IObjectWithId<object>
   {
        private object objID1;
        private int objID2;

        object IEntityBase.ID 
        { 
            get { return objID1; } 
            set { objID1 = value; } 
        }

        object IObjectWithId<object>.ID 
        {
            get { return objID2; } 
            set { objID2 = value; } 
        }
   }

看 - 这两个属性可以有不同的实现,此时运行时无法知道它们的实现是否统一(反射现在发生在运行时,而不是编译时进行统一的时间)。您收到的AmbiguousMatchException 是 .NET 框架防止您执行具有可能未知/意外行为的代码的方法。

当没有为每个接口提供不同的实现时(如您的情况),您拥有的唯一实现由该签名的方法表中的两个条目调用,但仍然有 两个条目指向同一个属性。为防止框架混乱,您应该在继承层次结构中使用足够高的类型,以便它在其方法表中只有一个条目用于您要反映的成员。在我们的示例中,如果我们在反映 Id 属性时使用 interface 类型,我们将解决我们的问题,因为每个接口在其方法表中只有一个用于请求签名的条目。

然后你可以使用

Console.WriteLine(
    typeof(IEntityBase).GetProperty(
        "Id", BindingFlags.Instance | BindingFlags.Public));

Console.WriteLine(
    typeof(BusinessObject<object>).GetProperty(
        "Id", BindingFlags.Instance | BindingFlags.Public));

取决于您要检索的实现。在我的最新示例中,每个接口都有不同的实现,您可以通过选择正确的接口来调用反射的任何实现。在您问题的示例中,您可以使用所需的任何接口,因为两者都有一个实现。

【讨论】:

  • 对不起,我还是不明白。如果 EntityBase 继承了两个 Id 属性,但编译器让它们合并为一个 - EntityBase.Id,那么这应该是句号,即 EntityBase 有一个 Id 属性。它应该是由反射返回的。无论如何,我必须在 .NET 4 上检查它,因为这似乎是我机器上安装 .NET 3.5 的错误。
  • 我已经编辑了我的帖子并提供了进一步的解释。不同框架的差异可能来自反射的工作方式。有一个绑定标志可以让您搜索给定类型的属性或继承的属性。在第二种情况下,您将遇到模棱两可的匹配异常。至少,这就是我认为不同的人对相同代码得到不同结果的方式——因为不同的框架版本在内部使用不同的标志。如果是这样的话,它可能被认为是框架中的一个错误。
  • 好的,现在我明白了。有没有办法检查类型的内部结构?使用某种外部工具?
  • 是的,您可以通过检查 Visual Studio 中的对象浏览器获得足够的公共成员信息。您可以在那里看到类的基本类型,还可以切换继承成员的可见性。但是,如果您需要查看对象的私有字段,类似于 Red Gate 的反射器的工具可能会很有用。不幸的是,它已成为付费工具。您需要与您的案例相关的是,如果具有相同签名的成员来自多个接口和/或某个类型的基类。请记住,根据定义,返回类型不是签名的一部分。
【解决方案2】:

当一个接口方法有两个实现时,我收到了这个错误。

【讨论】:

    猜你喜欢
    • 2018-11-30
    • 1970-01-01
    • 1970-01-01
    • 2011-02-11
    • 1970-01-01
    • 2022-06-11
    • 2013-10-22
    • 2014-01-09
    • 1970-01-01
    相关资源
    最近更新 更多