Dot-Net

誰能向我解釋為什麼以下程式碼會引發 System.Reflection.AmbiguousMatchException?

  • February 5, 2019
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 Framework

版本 3.5 SP1

編輯:

奇怪的是,看起來其他人無法重現它。雖然它每次都會在我的機器上崩潰。我發現這段程式碼:

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

工作正常,雖然它應該是一樣的。

為了回答你的問題,我會讓你熟悉術語“方法表”。這是 .NET 框架中類型的內部表示的一部分,其中每個 .NET 類型都有自己的方法表。您可以將其想像為包含該類型的所有方法和屬性的雜湊映射(或字典)。關鍵是方法/屬性簽名(方法名稱和參數類型,沒有返回類型),值是匹配方法/屬性的集合,以及一些反射元數據資訊,例如哪個類型聲明了方法/屬性。

當類A派生自基類B或實現介面C時,方法表中的項將直接在方法表中B可用。如果方法表已經包含具有特定簽名的項目,則該項目將添加到相同簽名的集合中,因此現在將具有簽名指向的 2 個方法/屬性。區分這些重複條目的唯一方法是比較描述簽名所代表聲明的類型的元數據。C``A``A``A

讓我們來看看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; }
  }

因為IEntityBase沒有實施。同樣,以下操作也會失敗:

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

因為這一次IObjectWithId<int>不滿足。您可以嘗試這樣做:

  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屬性由於它們相同的簽名而**統一。**所以這個類:

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

將編譯,因為該ID屬性同時滿足兩個介面。但是,該類在其方法表中EntityBase仍然有兩個屬性(一個來自每個介面)。 ID這兩個屬性由編譯器自動分配給EntityBase類中的相同實現(該過程稱為統一)。

以下程式碼:

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類型而不是,我們將解決我們的情況,因為每個介面在其方法表中只有一個條目用於請求的簽名。

然後你可以使用

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

要麼

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

取決於您要檢索的實現。在我的最新範例中,每個介面都有不同的實現,您可以通過選擇正確的介面來呼叫反射的任何實現。在您問題的範例中,您可以使用所需的任何介面,因為兩者都有一個實現。

引用自:https://stackoverflow.com/questions/2745853