任何人都可以向我解释为什么以下代码抛出System.Reflection.AmbiguousMatchException?

mar*_*ark 16 .net reflection

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));
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我得到这个:

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: 
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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

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

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

Iva*_*vov 18

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

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

让我们来看IObjectWithId<TId>定义属性的接口TId ID { get; set; }.的EntityBase类实现IObjectWithId<TId>如此接收TId ID { get; set; }属性及其方法表.同时,这个类实现了IEntityBase接口,为它提供了Object ID { get; set; }属性.然后,EntityBase该类在相同的签名下接收两个属性(因为返回类型不参与签名),而它仍将暴露2个不同的属性.以下声明将导致编译错误:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        public int ID { get; set; }
   }
Run Code Online (Sandbox Code Playgroud)

因为IEntityBase没有实现.同样,后续也将失败:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        public object ID { get; set; }
   }
Run Code Online (Sandbox Code Playgroud)

因为这个时间IObjectWithId<int>不满意.您可以尝试这样做:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        public object ID { get; set; }
        public int ID { get; set; }
   }
Run Code Online (Sandbox Code Playgroud)

只是为了获得另一个具有相同签名的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; } }
   }
Run Code Online (Sandbox Code Playgroud)

现在,回到您的代码 - 您使用object而不是TId创建一个罕见但有趣的案例 - 这两个ID属性由于其相同的签名而统一.所以这堂课:

   public class EntityBase : IEntityBase, IObjectWithId<object>
   {
        public object ID { get; set; }
   }
Run Code Online (Sandbox Code Playgroud)

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

以下代码:

typeof(EntityBase).GetProperty(
    "ID", BindingFlags.Instance | BindingFlags.Public);
Run Code Online (Sandbox Code Playgroud)

将查看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; } 
        }
   }
Run Code Online (Sandbox Code Playgroud)

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

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

然后你可以使用

Console.WriteLine(
    typeof(IEntityBase).GetProperty(
        "Id", BindingFlags.Instance | BindingFlags.Public));
Run Code Online (Sandbox Code Playgroud)

要么

Console.WriteLine(
    typeof(BusinessObject<object>).GetProperty(
        "Id", BindingFlags.Instance | BindingFlags.Public));
Run Code Online (Sandbox Code Playgroud)

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