Синтаксис с использованием LINQ ToList для приведения списка GENERIC к списку его базового типа

Я рассмотрел множество вопросов и ответов о стековом потоке, касающихся ToList и общих ограничений, но я не нашел такого, который объясняет "синтаксическую ошибку" в последнем возвращении ниже. Почему я должен явно Select а также cast элементы ("B")?

public interface I1
{
}
public class C2 : I1
{
    public static List<I1> DowncastListA( List<C2> list )
    {
        // "A": A concrete class works.
        return list == null ? null : list.ToList<I1>();
    }

    public static List<I1> DowncastListB<T2>( List<T2> list ) where T2 : I1
    {
        // "B": WORKS, if explicitly Select and Cast each element.
        return list == null ? null : list.Select( a => (I1)a ).ToList();
    }

    public static List<I1> DowncastListC<T2>( List<T2> list ) where T2 : I1
    {
        // "C": Syntax Error: 'List<T2>' does not contain a definition for 'ToList' and the best extension method overload 'ParallelEnumerable.ToList<I1>(ParallelQuery<I1>)' requires a receiver of type 'ParallelQuery<I1>'
        return list == null ? null : list.ToList<I1>();
    }
}

Некоторые связанные вопросы:
/questions/20763685/c-universalnyij-gde-ogranichenie-s-opredeleniem-lyuboj-universalnyij-tip/20763694#20763694
Как преобразовать список в список;

2 ответа

Решение

Метод расширения IEnumerable<T>.ToList<T>() не позволяет указывать тип цели. T это тип источника IEnumerable (что неявно известно из исходной коллекции).

Вместо этого вы можете использовать это:

public static List<I1> DowncastListC<T2>( List<T2> list ) where T2 : I1
{
    return list == null ? null : list.Cast<I1>().ToList();
}

Т.е. вы сначала разыгрываете каждый элемент (в результате чего IEnumerable<I1>), затем создайте список из этого.

Кстати, вы могли бы даже сделать это методом расширения, чтобы упростить его использование:

public static class Extensions
{
    public static List<I1> Downcast<T2>(this List<T2> list) where T2 : I1
    {
        return list == null ? null : list.Cast<I1>().ToList();
    }
}

По предложению @AluanHaddad

    public static IReadOnlyList<I1> DowncastList<T2>( List<T2> list ) where T2 : class, I1
    {
        return list;
    }

Обратите внимание на добавленное ограничение T2 : class,
Этот ответ не требует кастинга list, так как IReadOnlyList<T> является ковариантным, и в списке уже есть члены, которые реализуют I1. (Может альтернативно сделать тип возврата IEnumerable<I1>, но мне нужно было индексировать, поэтому решил выставить более высокий интерфейс.)

Или как альтернатива, если хотите выставить полную List функциональность:

    public static List<I1> DowncastList<T2>( List<T2> list ) where T2 : class, I1
    {
        return list == null ? null : list.ToList<I1>();
    }

Обратите внимание на добавленное ограничение T2 : class, Это дает достаточно информации для IEnumerable<T2> (который List<T2> Implements), чтобы найти реализацию ToList<>`.

Теперь, когда это работает, вот вторая версия выше, использующая условный ноль C# 6:

    public static List<I1> DowncastList<T2>( List<T2> list ) where T2 : class, I1
    {
        return list?.ToList<I1>();
    }
Другие вопросы по тегам