Возвращаемый тип членов в реализации интерфейса должен точно соответствовать определению интерфейса?

Согласно спецификации языка 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 вы можете делать такие вещи (ковариация и противоречие), но не совсем так, это неверно на каждом языке.