Почему ToList <Interface> не работает для типов значений?

Если я реализую интерфейс для типа значения и пытаюсь применить его к типу интерфейса этого списка, почему это приводит к ошибке, тогда как ссылочный тип преобразует только штраф?

Это ошибка:

Невозможно преобразовать тип аргумента экземпляра System.Collections.Generic.List<MyValueType> до System.Collections.Generic.IEnumerable<MyInterfaceType>

Мне нужно явно использовать метод Cast<T> для его преобразования, почему? Поскольку IEnumerable представляет собой перечисление только для чтения через коллекцию, для меня не имеет никакого смысла, что он не может быть передан напрямую.

Вот пример кода для демонстрации проблемы:

    public interface I{}

    public class T : I{}

    public struct V: I{}

    public void test()
    {
        var listT = new List<T>();
        var listV = new List<V>();

        var listIT = listT.ToList<I>();     //OK
        var listIV = listV.ToList<I>();     //FAILS to compile, why?

        var listIV2 = listV.Cast<I>().ToList(); //OK

    }

Ответы

Ответ 1

Отклонение (ковариация или контравариантность) не работает для типов значений, только ссылочные типы:

Отклонение применяется только к ссылочным типам; , если вы укажете тип значения для параметра типа варианта, , тип type является инвариантным для полученного построенного типа. (MSDN)

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

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

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


Объедините вышеуказанное с тем, как работают операции LINQ:

Операция

A Cast активизирует/блокирует все элементы (путем доступа к ним через не общий IEnumerable, как вы указали), а затем проверяет, что все элементы в последовательности могут быть успешно выбраны/распакованы в предоставленный тип а затем делает именно это. Операция ToList перечисляет последовательность и возвращает список из этого перечисления.

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