Может кто-нибудь объяснить мне, почему следующий код выдает System.Reflection.AmbiguousMatchException? - PullRequest
15 голосов
/ 30 апреля 2010
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

EDIT:

Как ни странно, похоже, что другие люди не могут воспроизвести его. Хотя на моей машине каждый раз происходит сбой. Я узнал, что этот код:

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

Работает нормально, хотя должно быть так же.

Ответы [ 2 ]

18 голосов
/ 14 июня 2011

Чтобы ответить на ваш вопрос, я познакомлю вас с термином «Таблица методов». Это часть внутреннего представления типов в .NET Framework, где каждый тип .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; }
   }

потому что 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 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; } 
        }
   }

См. - эти два свойства могут иметь разных реализаций , и в этот момент среда выполнения не может знать, унифицированы ли их реализации (отражение происходит сейчас во время 1067 *, а не во время компиляции, унификация была выполнена). AmbiguousMatchException, который вы получили, - это способ .NET Framework предотвратить выполнение кода с возможно неизвестным / непреднамеренным поведением.

Когда для каждого интерфейса не предусмотрено никакой другой реализации (как в вашем случае), единственная ваша реализация вызывается обеими записями в таблице методов для этой сигнатуры, но все же есть две записи , указывающие в ту же собственность. Чтобы избежать путаницы в среде, вы должны использовать тип достаточно высокий в иерархии наследования, чтобы в его таблице методов была только одна запись для элемента, который вы хотите отразить. В нашем примере, если мы вместо этого будем использовать типы interface при отражении свойства Id, мы решим наш случай, поскольку каждый из интерфейсов имеет только одну запись в своей таблице методов для запрошенной подписи.

Вы можете использовать

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

или

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

в зависимости от того, какую реализацию вы хотите получить. В моем последнем примере, где каждый интерфейс имеет различную реализацию, у вас есть возможность вызвать рефлексивную любую из реализаций, выбрав правильный интерфейс. В примере из вашего вопроса вы можете использовать любой интерфейс, который вам нужен, поскольку оба имеют одну реализацию.

0 голосов
/ 05 февраля 2019

Я получил эту ошибку, когда было две реализации для одного метода интерфейса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...