Возвращаемый тип членов в реализации интерфейса должен точно соответствовать определению интерфейса?
Согласно спецификации языка CSharp.
Интерфейс определяет контракт, который может быть реализован классами и Структуры. Интерфейс не обеспечивает реализацию элементов он определяет - он просто указывает членов, которые должны быть предоставлены классы или структуры, реализующие интерфейс.
Итак, у меня есть это:
interface ITest
{
IEnumerable<int> Integers { get; set; }
}
И я имею в виду. "У меня есть контракт с свойством как совокупность целых чисел, которые вы можете перечислить".
Затем мне нужен следующий интерфейс. Реализация:
class Test : ITest
{
public List<int> Integers { get; set; }
}
И я получаю следующую ошибку компилятора:
'Test' не реализует член интерфейса 'ITest.Integers'. "Test.Integers" не может реализовать "ITest.Integers", потому что он не имеют соответствующий тип возврата 'System.Collections.Generic.IEnumerable'.
Пока я могу сказать, что мой класс Test реализует контракт ITest, потому что свойство List of int на самом деле является IEnumerable из int.
Итак, компилятор С# говорит мне об ошибке?
Ответы
Ответ 1
Вы не можете сделать этого, потому что у вас будет серьезная проблема в вашей руке в зависимости от реализации, если это было разрешено. Рассмотрим:
interface ITest
{
IEnumerable<int> Integers { get; set; }
}
class Test : ITest
{
// if this were allowed....
public List<int> Integers { get; set; }
}
Это позволит:
ITest test = new Test();
test.Integers = new HashSet<int>();
Это приведет к недействительности контракта для теста, потому что Test говорит, что он содержит List<int>
.
Теперь вы можете использовать явную реализацию интерфейса, чтобы он мог удовлетворять обеим подписям в зависимости от того, вызвал ли он ссылку ITest
или ссылку Test
:
class Test : ITest
{
// satisfies interface explicitly when called from ITest reference
IEnumerable<int> ITest.Integers
{
get
{
return this.Integers;
}
set
{
this.Integers = new List<int>(value);
}
}
// allows you to go directly to List<int> when used from reference of type Test
public List<int> Integers { get; set; }
}
Ответ 2
FYI, функция, которую вы хотите, называется "ковариация типа возвращаемого метода", и, как вы обнаружили, она не поддерживается С#. Это особенность других объектно-ориентированных языков, таких как С++.
Хотя мы часто запрашиваем эту функцию, мы не планируем добавлять ее на язык. Это не ужасная особенность; если бы у нас это было, я бы использовал его. Но у нас есть много причин не делать этого, в том числе, что он не поддерживается CLR, он добавляет новые и интересные режимы отказа к версиям компонентов, Anders не считает, что это очень интересная функция, и у нас есть много, много более высоких приоритетов и ограниченный бюджет.
Кстати, хотя люди все время спрашивают нас о возможности ковариации возвращаемого типа виртуального метода, никто никогда не спрашивает о контравариантности формального параметра виртуального метода, хотя логически они по сути являются одной и той же функцией. То есть, у меня есть метод виртуального метода/интерфейса M, который принимает Жирафа, и я хотел бы переопределить его/реализовать его с помощью метода M, который принимает Animal.
Ответ 3
Простой факт: если интерфейс говорит:
IInterface{
Animal A { get; }
}
Тогда реализация этого свойства должна соответствовать типу точно. Попытка реализовать его как
MyClass : IInterface{
Duck A { get; }
}
Не работает - даже если Duck
является Animal
Вместо этого вы можете сделать это:
MyClass : IInterface{
Duck A { get; }
Animal IInterface.A { get { return A; } }
}
т.е. обеспечивают явную реализацию члена IInterface.A
, используя отношение типа между Duck
и Animal
.
В вашем случае это означает реализацию, по крайней мере, геттера, ITest.Integers как
IEnumerable<int> ITest.Integers { get { return Integers; } }
Чтобы реализовать сеттер, вам нужно будет оптимизировать себя или использовать .ToList() для входного значения.
Обратите внимание, что использование A
и Integers
внутри этих явных реализаций не является рекурсивным, потому что явная реализация интерфейса скрыта от общедоступного представления типа - они только пинают, когда вызывающий абонент разговаривает с типом через него IInterface
/ITest
.
Ответ 4
Вам нужно 13.4.4 из спецификации:
Для целей сопоставления интерфейсов член класса A
соответствует члену интерфейса B
, когда:
• A
и B
являются свойствами, имя и тип для A и B идентичны, а A имеет те же аксессоры, что и B
(A
разрешено иметь дополнительных аксессуаров, если это не явная реализация элемента интерфейса).
Кроме того, ваше убеждение, что List<int> Integers { get; set; }
удовлетворяет контракту IEnumerable<int> Integers { get; set; }
, является ложным. Даже если спецификация была каким-то образом смягчена, чтобы не требовать идентичности типов возврата, обратите внимание, что свойство типа List<int>
с публичным сетевым устройством нигде не похоже на свойство типа IEnumerable<int>
с общедоступным сетевым устройством, поскольку последний вы можете назначить экземпляр int[]
, но первому вы не можете.
Ответ 5
Вы можете сделать что-то вроде этого:
interface ITest
{
IEnumerable<int> Integers { get; set; }
}
class Test : ITest
{
public IEnumerable<int> Integers { get; set; }
public Test()
{
Integers = new List<int>();
}
}
Ответ 6
Потому что Test не является ITest. Зачем? С помощью ITest вы можете установить массив в свойство Integer. Но вы не можете с тестом.
С .net 4.0 вы можете делать такие вещи (ковариация и противоречие), но не совсем так, это неверно на каждом языке.