Проблема понимания ковариационной контравариантности с дженериками в С#

Я не понимаю, почему следующий код С# не компилируется.

Как вы можете видеть, у меня есть статический общий метод. Что-то с параметром IEnumerable<T>T ограничено как интерфейс IA), и этот параметр не может быть неявно преобразован в IEnumerable<IA>.

Какое объяснение? (Я не ищу обходного пути, просто чтобы понять, почему он не работает).

public interface IA { }
public interface IB : IA { }
public class CIA : IA { }
public class CIAD : CIA { }
public class CIB : IB { }
public class CIBD : CIB { }

public static class Test
{
    public static IList<T> Something<T>(IEnumerable<T> foo) where T : IA
    {
        var bar = foo.ToList();

        // All those calls are legal
        Something2(new List<IA>());
        Something2(new List<IB>());
        Something2(new List<CIA>());
        Something2(new List<CIAD>());
        Something2(new List<CIB>());
        Something2(new List<CIBD>());
        Something2(bar.Cast<IA>());

        // This call is illegal
        Something2(bar);

        return bar;
    }

    private static void Something2(IEnumerable<IA> foo)
    {
    }
}

Ошибка. Я попал в Something2(bar):

Аргумент 1: невозможно преобразовать из 'System.Collections.Generic.List' в 'System.Collections.Generic.IEnumerable'

Ответ 1

Сообщение об ошибке недостаточно информативно, и это моя ошибка. Извини за это.

Проблема, которую вы испытываете, является следствием того, что ковариация работает только по ссылочным типам.

Вероятно, вы говорите "но IA является ссылочным типом" прямо сейчас. Да, это. Но вы не сказали, что T равно IA. Вы сказали, что T - тип, который реализует IA, а тип значения может реализовать интерфейс. Поэтому мы не знаем, будет ли ковариация работать, и мы ее не разрешаем.

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

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

Ответ 2

Я просто хотел дополнить Eric отличным инсайдерским ответом с примером кода для тех, кто, возможно, не знаком с общими ограничениями.

Измените Something вроде такой: ограничение class должно быть первым.

public static IList<T> Something<T>(IEnumerable<T> foo) where T : class, IA

Ответ 3

Проблема, которую вы испытываете, является следствием того, что ковариация работает только по ссылочным типам.

Ковариантные и контравариантные преобразования интерфейсов и типов делегатов требуют, чтобы все аргументы различного типа были ссылочными.

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

Самый простой способ гарантировать, что все нетривиальные преобразования в аргументах типа сохраняются при сохранении идентичности, - это ограничить их ссылочными преобразованиями.

Ковариация и контравариантность являются законными только тогда, когда справедливы все следующие:

(1) дисперсия доказуемо типична,

(2) автор добавленных аннотаций вариации типа, указывающий желаемые co- и противоположные дисперсии,

(3) используемые аргументы различного типа - это все ссылочные типы,

(4) общий тип является либо делегатом, либо интерфейсом.