С# Ковариация по типам возвратов подкласса

Кто-нибудь знает, почему ковариантные типы возврата не поддерживаются в С#? Даже когда вы пытаетесь использовать интерфейс, компилятор жалуется, что он не разрешен. См. Следующий пример.

class Order
{
    private Guid? _id;
    private String _productName;
    private double _price;

    protected Order(Guid? id, String productName, double price)
    {
        _id = id;
        _productName = productName;
        _price = price;
    }

    protected class Builder : IBuilder<Order>
    {
        public Guid? Id { get; set; }
        public String ProductName { get; set; }
        public double Price { get; set; }

        public virtual Order Build()
        {
            if(Id == null || ProductName == null || Price == null)
                throw new InvalidOperationException("Missing required data!");

            return new Order(Id, ProductName, Price);
        }
    }            
}

class PastryOrder : Order
{
    PastryOrder(Guid? id, String productName, double price, PastryType pastryType) : base(id, productName, price)
    {

    }

    class PastryBuilder : Builder
    {
        public PastryType PastryType {get; set;}

        public override PastryOrder Build()
        {
            if(PastryType == null) throw new InvalidOperationException("Missing data!");
            return new PastryOrder(Id, ProductName, Price, PastryType);
        }
    }
}

interface IBuilder<in T>
{
    T Build();
}

public enum PastryType
{
    Cake,
    Donut,
    Cookie
}

Спасибо за любые ответы.

Ответ 1

Во-первых, тип возврата контравариантность не имеет никакого смысла; Я думаю, вы говорите о типе возврата ковариации.

См. этот вопрос для деталей:

Поддерживает ли С# поддержку ковариации типа типа?

Вы хотите знать, почему функция не реализована. phoog правильный; эта функция не реализована, потому что никто ее здесь не реализовал. Необходимым, но недостаточным требованием является то, что преимущества функции превышают затраты.

Затраты значительны. Функция не поддерживается из-за среды выполнения, она работает прямо против нашей цели сделать С# версией, потому что она представляет еще одну форму хрупкой проблемы базового класса, Anders не считает, что это интересная или полезная функция, и если вы действительно хотите, вы можете заставить его работать, написав небольшие вспомогательные методы. (Это именно то, что делает версия CIL С++).

Преимущества небольшие.

Высокая стоимость, небольшие преимущества с легким обходом обходятся очень быстро. У нас гораздо более высокие приоритеты.

Ответ 2

Контравариантный общий параметр не может быть выведен, потому что это невозможно гарантировать во время компиляции, и разработчики С# приняли решение не продлевать необходимые проверки во время выполнения.

Это короткий ответ, и вот немного длиннее...

Что такое дисперсия?

Отклонение - это свойство преобразования, применяемого к иерархии типов:

  • Если результатом преобразования является иерархия типов, которая сохраняет "направление" исходной иерархии типов, преобразование co -вариантно.
  • Если результатом преобразования является иерархия типов, которая меняет исходное "направление", преобразование contra -вариантно.
  • Если результат преобразования представляет собой связку несвязанных типов, преобразование в -варианте.

Что такое отклонение от С#?

В С# "преобразование" используется как "общий параметр". Например, допустим, класс Parent наследуется классом Child. Обозначим этот факт как: Parent > Child (потому что все экземпляры Child также являются экземплярами Parent, но не обязательно наоборот, поэтому Parent "больше" ). Пусть также говорят, что у нас есть общий интерфейс I<T>:

  • Если I<Parent> > I<Child>, T ковариантно (исходное "направление" между Parent и Child сохраняется).
  • Если I<Parent> < I<Child>, T контравариантно (исходное "направление" обращено).
  • Если I<Parent> не связано с I<Child>, T инвариантно.

Итак, что потенциально опасно?

Если компилятор С# действительно согласился скомпилировать следующий код...

class Parent {
}

class Child : Parent {
}

interface I<in T> {
    T Get(); // Imagine this actually compiles.
}

class G<T> : I<T> where T : new() {
    public T Get() {
        return new T();
    }
}

// ...

I<Child> g = new G<Parent>(); // OK since T is declared as contravariant, thus "reversing" the type hierarchy, as explained above.
Child child = g.Get(); // Yuck!

... это приведет к проблеме во время выполнения: a Parent создается и привязывается к ссылке на Child. Поскольку Parent не Child, это неверно!

Последняя строка выглядит ОК во время компиляции, так как I<Child>.Get объявляется как возвращаемый Child, но мы не можем полностью "доверять" его во время выполнения. Дизайнеры С# решили сделать правильные вещи и полностью поймать проблему во время компиляции и избежать необходимости в проверках времени выполнения (в отличие от массивов).

(Для подобных, но "обратных" причин ковариантный общий параметр не может использоваться как вход.)

Ответ 3

Эрик Липперт написал несколько сообщений на этом сайте о ковариантности метода возврата при переопределении метода, насколько я могу видеть, почему функция не поддерживается. Он упомянул, однако, что нет никаких планов поддержать его: fooobar.com/questions/40170/...

Эрик также любит говорить, что ответ на "почему не поддерживается X" всегда один и тот же: поскольку никто не проектировал, не реализовывал и не тестировал (и т.д.) X. Пример этого здесь: fooobar.com/questions/40174/...

Может быть какая-то философская причина отсутствия этой функции; возможно, Эрик увидит этот вопрос и просветит нас.

ИЗМЕНИТЬ

Как заметил Пратик в комментарии:

interface IBuilder<in T> 
{ 
    T Build(); 
} 

должен быть

interface IBuilder<out T> 
{ 
    T Build(); 
} 

Это позволит вам реализовать PastryOrder : IBuilder<PastryOrder>, и вы могли бы иметь

IBuilder<Order> builder = new PastryOrder();

Есть, вероятно, два или три подхода, которые вы могли бы использовать для решения своей проблемы, но, как вы заметили, ковариантность метода возврата не является одним из этих подходов, и ни одна из этих данных не отвечает на вопрос о том, почему С# не поддерживает ее.

Ответ 4

Просто, чтобы опубликовать это где-то, Google находит это... Я изучал это, потому что я хотел иметь интерфейс, в котором я могу возвращать коллекции/перечисления произвольных классов, реализующих определенный интерфейс.

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

Я привел пример, который может вам помочь.

Как отметил Бранко Димитриевич, обычно небезопасно допускать ковариантные типы возврата в целом. Но используя это, он безопасен по типу, и вы можете даже вложить его (например, interface A<T, U> where T: B<U> where U : C)

(Отказ от ответственности: я начал использовать С# вчера, поэтому, возможно, я ошибаюсь в отношении лучших практик, кто-то с большим опытом должен прокомментировать это:))


Пример:

Используя

interface IProvider<T, Coll> where T : ProvidedData where Coll : IEnumerable<T>
{
  Coll GetData();
}

class XProvider : IProvider<X, List<X>>
{
  List<X> GetData() { ... }
}

вызова

new XProvider().GetData

работает и в этом случае безопасен. Вам нужно только определить типы, которые вы хотите вернуть в этом случае.


Подробнее об этом: http://msdn.microsoft.com/de-de/library/d5x73970.aspx