C # Ковариация по типам возвращаемых подклассов - PullRequest
7 голосов
/ 11 февраля 2012

Кто-нибудь знает, почему ковариантные возвращаемые типы не поддерживаются в 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 ]

25 голосов
/ 11 февраля 2012

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

Подробности смотрите в этом вопросе:

Поддерживает ли C # ковариацию типа возврата?

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

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

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

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

9 голосов
/ 11 февраля 2012

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

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

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

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

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

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

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

  • Если I<Parent>> I<Child>, T является ковариантным (исходное «направление» между Parent и Child сохраняется).
  • Если I<Parent> <<code>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 # решили поступить правильно и полностью уловить проблему во время компиляции, избегая необходимости проверок во время выполнения (в отличие от массивов).

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

7 голосов
/ 11 февраля 2012

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

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

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

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

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

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

должно быть

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

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

IBuilder<Order> builder = new PastryOrder();

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

0 голосов
/ 28 марта 2013

Просто чтобы опубликовать это где-то, 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

...