C# Ковариация по типам возвращаемых подклассов

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

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
}

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

4 ответа

Решение

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

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

Поддерживает ли C# ковариацию типа возвращаемого значения?

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

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

Преимущества невелики.

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

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

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

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

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

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

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

В C# "преобразование" используется как общий параметр. Например, скажем, класс 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 инвариантен.

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

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

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!

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

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

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

Эрик Липперт написал несколько постов на этом сайте о ковариации метода возврата для переопределений методов, насколько я могу судить, почему эта функция не поддерживается. Он упомянул, однако, что нет никаких планов поддержать это: /questions/10844514/c-kovariantnyie-tipyi-vozvraschaemyih-dannyih-ispolzuyuschie-dzheneriki/10844533#10844533

Эрик также любит говорить, что ответ "почему не поддерживается X " всегда один и тот же: потому что никто не проектировал, не реализовывал и не тестировал (и т. Д.) X. Пример этого здесь: /questions/47234114/c-dzheneriki-bez-ogranichenij-po-dizajnu/47234139#47234139

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

РЕДАКТИРОВАТЬ

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

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

должно быть

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

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

IBuilder<Order> builder = new PastryOrder();

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

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

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

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

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

(Отказ от ответственности: я начал использовать 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

Другие вопросы по тегам