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