Syntax using LINQ ToList to cast GENERIC list to l

2019-08-26 10:25发布

问题:

I've looked at lots of stackoverflow Q&As about ToList and generic constraints, but I haven't found one that explains the "Syntax Error" in the final return below. Why do I have to explicitly Select and cast the elements ("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>();
    }
}

Some related Qs:
https://stackoverflow.com/a/1541173/199364
How to Cast List<T> To List<ClassName>

回答1:

The extension method IEnumerable<T>.ToList<T>() doesn't allow to specify a target type. T is the type of the source IEnumerable (which is implicitly known from the source collection).

Instead you can use this:

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

I.e. you first cast each element (resulting in an IEnumerable<I1>), then create a list from that.

BTW: you could even make that an extension method to simplify its usage:

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();
    }
}


回答2:

As suggested by @AluanHaddad

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

Note the added constraint T2 : class.
That answer doesn't require any casting of list, because IReadOnlyList<T> is covariant, and list already has members that implement I1. (Could alternatively make the return type IEnumerable<I1>, but I needed indexing, so chose to expose a higher interface.)

Or as an alternative, if wish to expose the full List functionality:

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

Note the added constraint T2 : class. This gives enough information for IEnumerable<T2> (which List<T2> implements) to find the implementation of ToList<>`.

Now that this works, here is second version above, using C# 6 null conditional:

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