开发者

Shorter syntax for casting from a List<X> to a List<Y>?

I know it's possible to cast a list of items from one type to another (given that your object has a public static explicit operator method to do the casting) one at a time as follows:

List<Y> ListOfY = new List<Y>();

foreach(X x in ListOfX)
    ListOfY.Add((Y)x);

But is it not po开发者_如何学Cssible to cast the entire list at one time? For example,

ListOfY = (List<Y>)ListOfX;


If X can really be cast to Y you should be able to use

List<Y> listOfY = listOfX.Cast<Y>().ToList();

Some things to be aware of (H/T to commenters!)

  • You must include using System.Linq; to get this extension method
  • This casts each item in the list - not the list itself. A new List<Y> will be created by the call to ToList().
  • This method does not support custom conversion operators. ( see Why does the Linq Cast<> helper not work with the implicit cast operator? )
  • This method does not work for an object that has an explicit operator method (framework 4.0)


The direct cast var ListOfY = (List<Y>)ListOfX is not possible because it would require co/contravariance of the List<T> type, and that just can't be guaranteed in every case. Please read on to see the solutions to this casting problem.

While it seems normal to be able to write code like this:

List<Animal> animals = (List<Animal>) mammalList;

because we can guarantee that every mammal will be an animal, this is obviously a mistake:

List<Mammal> mammals = (List<Mammal>) animalList;

since not every animal is a mammal.

However, using C# 3 and above, you can use

IEnumerable<Animal> animals = mammalList.Cast<Animal>();

that eases the casting a little. This is syntactically equivalent to your one-by-one adding code, as it uses an explicit cast to cast each Mammal in the list to an Animal, and will fail if the cast is not successfull.

If you like more control over the casting / conversion process, you could use the ConvertAll method of the List<T> class, which can use a supplied expression to convert the items. It has the added benifit that it returns a List, instead of IEnumerable, so no .ToList() is necessary.

List<object> o = new List<object>();
o.Add("one");
o.Add("two");
o.Add(3);

IEnumerable<string> s1 = o.Cast<string>(); //fails on the 3rd item
List<string> s2 = o.ConvertAll(x => x.ToString()); //succeeds


To add to Sweko's point:

The reason why the cast

var listOfX = new List<X>();
ListOf<Y> ys = (List<Y>)listOfX; // Compile error: Cannot implicitly cast X to Y

is not possible is because the List<T> is invariant in the Type T and thus it doesn't matter whether X derives from Y) - this is because List<T> is defined as:

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T> ... // Other interfaces

(Note that in this declaration, type T here has no additional variance modifiers)

However, if mutable collections are not required in your design, an upcast on many of the immutable collections, is possible, e.g. provided that Giraffe derives from Animal:

IEnumerable<Animal> animals = giraffes;

This is because IEnumerable<T> supports covariance in T - this makes sense given that IEnumerable implies that the collection cannot be changed, since it has no support for methods to Add or Remove elements from the collection. Note the out keyword in the declaration of IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

(Here's further explanation for the reason why mutable collections like List cannot support covariance, whereas immutable iterators and collections can.)

Casting with .Cast<T>()

As others have mentioned, .Cast<T>() can be applied to a collection to project a new collection of elements casted to T, however doing so will throw an InvalidCastException if the cast on one or more elements is not possible (which would be the same behaviour as doing the explicit cast in the OP's foreach loop).

Filtering and Casting with OfType<T>()

If the input list contains elements of different, incompatable types, the potential InvalidCastException can be avoided by using .OfType<T>() instead of .Cast<T>(). (.OfType<>() checks to see whether an element can be converted to the target type, before attempting the conversion, and filters out incompatable types.)

foreach

Also note that if the OP had written this instead: (note the explicit Y y in the foreach)

List<Y> ListOfY = new List<Y>();

foreach(Y y in ListOfX)
{
    ListOfY.Add(y);
}

that the casting will also be attempted. However, if no cast is possible, an InvalidCastException will result.

Examples

For example, given the simple (C#6) class hierarchy:

public abstract class Animal
{
    public string Name { get;  }
    protected Animal(string name) { Name = name; }
}

public class Elephant :  Animal
{
    public Elephant(string name) : base(name){}
}

public class Zebra : Animal
{
    public Zebra(string name)  : base(name) { }
}

When working with a collection of mixed types:

var mixedAnimals = new Animal[]
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};

foreach(Animal animal in mixedAnimals)
{
     // Fails for Zed - `InvalidCastException - cannot cast from Zebra to Elephant`
     castedAnimals.Add((Elephant)animal);
}

var castedAnimals = mixedAnimals.Cast<Elephant>()
    // Also fails for Zed with `InvalidCastException
    .ToList();

Whereas:

var castedAnimals = mixedAnimals.OfType<Elephant>()
    .ToList();
// Ellie

filters out only the Elephants - i.e. Zebras are eliminated.

Re: Implicit cast operators

Without dynamic, user defined conversion operators are only used at compile-time*, so even if a conversion operator between say Zebra and Elephant was made available, the above run time behaviour of the approaches to conversion wouldn't change.

If we add a conversion operator to convert a Zebra to an Elephant:

public class Zebra : Animal
{
    public Zebra(string name) : base(name) { }
    public static implicit operator Elephant(Zebra z)
    {
        return new Elephant(z.Name);
    }
}

Instead, given the above conversion operator, the compiler will be able to change the type of the below array from Animal[] to Elephant[], given that the Zebras can be now converted to a homogeneous collection of Elephants:

var compilerInferredAnimals = new []
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};

Using Implicit Conversion Operators at run time

*As mentioned by Eric, the conversion operator can however be accessed at run time by resorting to dynamic:

var mixedAnimals = new Animal[] // i.e. Polymorphic collection
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};

foreach (dynamic animal in mixedAnimals)
{
    castedAnimals.Add(animal);
}
// Returns Zed, Ellie


You can use List<Y>.ConvertAll<T>([Converter from Y to T]);


This is not quite the answer to this question, but it may be useful for some: as @SWeko said, thanks to covariance and contravariance, List<X> can not be cast in List<Y>, but List<X> can be cast into IEnumerable<Y>, and even with implicit cast.

Example:

List<Y> ListOfY = new List<Y>();
List<X> ListOfX = (List<X>)ListOfY; // Compile error

but

List<Y> ListOfY = new List<Y>();
IEnumerable<X> EnumerableOfX = ListOfY;  // No issue

The big advantage is that it does not create a new list in memory.


In case when X derives from Y you can also use ToList<T> method instead of Cast<T>

listOfX.ToList<Y>()


dynamic data = List<x> val;  
List<y> val2 = ((IEnumerable)data).Cast<y>().ToList();
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜