Ошибка преобразования типа С#, несмотря на общее ограничение

Почему с общим ограничением на параметр типа T класса P «должен наследовать от A» первый вызов завершается успешно, но второй вызов завершается ошибкой с ошибкой преобразования типа, подробно описанной в комментарии:

abstract class A { }

static class S
{
    public static void DoFirst(A argument) { }
    public static void DoSecond(ICollection<A> argument) { }
}

static class P<T>
    where T : A, new()
{
    static void Do()
    {
        S.DoFirst(new T());             // this call is OK

        S.DoSecond(new List<T>());      // this call won't compile with:

        /* cannot convert from 'System.Collections.Generic.List<T>'
           to 'System.Collections.Generic.ICollection<A>' */
    }
}

Разве общее ограничение не должно гарантировать, что List<T> является действительно ICollection<A>?


person acdx    schedule 31.07.2009    source источник
comment
Закрытие как дубликат - см. "> stackoverflow.com/questions/981570/   -  person Pavel Minaev    schedule 01.08.2009


Ответы (3)


Это пример отсутствия в C# ковариантности для универсальных типов (C# поддерживает ковариацию массивов). C# 4 добавит эту функцию в типы интерфейсов, а также обновит несколько типов интерфейсов BCL для ее поддержки.

См. C# 4.0: ковариантность и контравариантность:

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

person Andrew Hare    schedule 31.07.2009
comment
Это не изменится в C# 4.0. IList<T> и ICollection<T> не ковариантны. Таким образом, несмотря на то, что в C# 4.0 будет добавлена ​​поддержка ковариантности типов, этот конкретный случай не будет работать. - person Pavel Minaev; 01.08.2009
comment
Павел, вы, кажется, тут запутались. Используемые типы НЕ IList‹T› и ICollection‹T›... это List‹T› (конкретный тип, а не интерфейс) и ICollection‹T›. Список‹T› — это АБСОЛЮТНО ICollection‹T›, и это делает их ковариантными. Вы разместили свой комментарий в ТРЕХ сообщениях и, по-видимому, проголосовали против двух из них... на основании неправильного типа... это не IList‹T›, это List‹T›! - person jrista; 01.08.2009

Ограничение не влияет на задачу; проблема в том, что вы передаете список в параметре, для которого требуется ICollection - C# не поддерживает ковариантность, поэтому вам нужно явно привести список к ICollection:

S.DoSecond((ICollection<A>) new List<T>());      // this call will be happy
person STW    schedule 31.07.2009
comment
Проблема не в несоответствии IList/ICollection, проблема в несоответствии аргументов типа. - person Pavel Minaev; 01.08.2009

Вы строго типизировали параметр для DoSecond как тип ICollection‹A›. Несмотря на то, что T имеет тип A, во время компиляции между List‹T› и ICollection‹A› нет неявного приведения. Вам нужно либо создать список и привести его к ICollection‹A› при вызове DoSecond, либо сделать DoSecond универсальным методом.

ПРИМЕЧАНИЕ. Этот тип неявного приведения должен поддерживаться в C# 4.0, что обеспечит значительно улучшенную совместимость/контравариантность по сравнению с тем, что предлагает C# 3.0.

person jrista    schedule 31.07.2009
comment
Э-э, да, между T и A существует неявное восходящее преобразование. Между IList<T> и IList<A> нет восходящего преобразования. Кроме того, здесь не поможет поддержка ковариантности в C# 4.0, потому что ни IList<T>, ни ICollection<T> сами по себе не являются ковариантными. - person Pavel Minaev; 01.08.2009
comment
Я отредактировал свой пост, чтобы уточнить мой смысл. Использовались следующие типы: List‹T› и ICollection‹A›. List‹T› действительно является ICollection‹T›, и поскольку T принадлежит A, ICollection‹T› должен быть ковариантным с ICollection‹A› в C# 4.0. Я извиняюсь за первоначальную неясность... надеюсь, мое редактирование сделает это более понятным. - person jrista; 01.08.2009