从List <X>转换为List <Y>的语法较短吗?


237

我知道可以一次将一种类型的项目列表转换为另一种类型(假设您的对象具有公共静态显式运算符方法进行转换),如下所示:

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

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

但是不可能一次投射整个列表吗?例如,

ListOfY = (List<Y>)ListOfX;

@Oded:我只是想让它更清晰一点。别担心,您不是,我了解:)
BoltClock

1
假设X从Y派生,Z从Y派生,请考虑一下,如果将Z添加到List <Y>(实际上是List <X>)中会发生什么。
理查德·

Answers:


497

如果X真的可以投给Y你应该可以使用

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

需要注意的一些事情(向评论者致敬!)


12
还有另一个金牌。这非常有用。
ouflak

6
必须包含以下行才能使编译器识别那些扩展方法:using System.Linq;
hypehuman

8
还应注意,尽管这会强制转换列表中的每个项目,但列表本身不会被转换。而是创建具有所需类型的新列表。
hypehuman 2015年

4
另请注意,该Cast<T>方法不支持自定义转换运算符。为什么Linq Cast帮助程序不能与隐式Cast操作符一起使用
clD

对于具有显式运算符方法(框架4.0)的对象不起作用
Adrian

100

直接铸造var ListOfY = (List<Y>)ListOfX是不可能的,因为它需要合作/逆变中的List<T>类型,而只是不能在任何情况下得到保证。请继续阅读以查看此铸造问题的解决方案。

能够像这样写代码似乎很正常:

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

因为我们可以保证每个哺乳动物都会成为动物,所以这显然是一个错误:

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

因为并非每只动物都是哺乳动物。

但是,使用C#3及更高版本,您可以使用

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

这样可以减轻铸造的负担。从语法上讲,这等效于您的一对一添加代码,因为它使用显式强制转换将Mammal列表中的每个强制转换为Animal,并且如果强制转换不成功将失败。

如果您希望对转换/转换过程进行更多控制,则可以使用类的ConvertAll方法,该方法List<T>可以使用提供的表达式转换项目。它具有返回的List而不是的好处IEnumerable,因此没有.ToList()必要。

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

2
我无法相信,直到现在为止,香港专业教育学院从未对此答案+1。它比上面的我好得多。
Jamiec 2014年

6
@Jamiec我没有+1,因为他以“不,这不可能”开头,同时掩盖了许多发现此问题的人所寻找的答案。从技术上讲,他确实更彻底地回答了OP的问题。
丹·贝查德

13

要补充Sweko的观点:

之所以投

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

不可能是因为类型TList<T>in不变的,因此是否X从派生无关紧要Y-这是因为List<T>定义为:

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

(请注意,在此声明中,T此处键入没有其他方差修饰符)

但是,如果您的设计中不需要可变的集合,则可以对许多不可变的集合进行向上转换,例如,提供的Giraffe源于Animal

IEnumerable<Animal> animals = giraffes;

这是因为IEnumerable<T>支持in的协方差T-考虑到这IEnumerable意味着不能更改集合,这是有意义的,因为它不支持从集合中添加或删除元素的方法。注意以下out声明中的关键字IEnumerable<T>

public interface IEnumerable<out T> : IEnumerable

这里是进一步的解释,以说明为什么可变的collection之类List不能支持covariance,而不变的迭代器和collection可以支持。)

搭配 .Cast<T>()

正如其他人提到的那样,.Cast<T>()可以将其应用于集合以投射强制转换为T的元素的新集合,但是InvalidCastException如果无法对一个或多个元素进行强制转换,则这样做将抛出(与显式操作的行为相同)。投放到OP的foreach循环中)。

过滤和投射 OfType<T>()

如果输入列表包含不同的,不兼容的类型的元素,则InvalidCastException可以通过使用.OfType<T>()代替来避免潜在的可能性.Cast<T>()。(.OfType<>()在尝试进行转换之前,检查是否可以将元素转换为目标类型,并过滤掉不兼容的类型。)

前言

还要注意的是,如果OP写了这个:(注意明确Y yforeach

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

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

也将尝试投射。但是,如果无法进行强制转换,InvalidCastException则会产生。

例子

例如,给定简单的(C#6)类层次结构:

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) { }
}

使用混合类型的集合时:

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

鉴于:

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

仅滤除大象-即消除了斑马。

回复:隐式强制转换运算符

如果没有动态的用户定义转换运算符,则仅在编译时使用 *,因此,即使说Zebra和Elephant之间的转换运算符可用,转换方法的上述运行时行为也不会改变。

如果我们添加转换运算符将Zebra转换为Elephant:

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

相反,给定上述转换操作符,编译器将能够将以下数组的类型从Animal[]更改为Elephant[],因为现在可以将Zebras转换为同类的Elephants集合:

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

在运行时使用隐式转换运算符

*如Eric所述,但是可以在运行时通过以下方式访问转换运算符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

嘿,我刚刚尝试使用以下示例“使用foreach()进行类型过滤”:var list = new List <object>(){1,“ a”,2,“ b”,3,“ c”,4,“ d“}; foreach(列表中的int)Console.WriteLine(i); 当我运行它时,我得到“指定的转换无效”。我想念什么吗?我不认为foreach这样工作,这就是为什么我要尝试。
布伦特·里滕豪斯'18

另外,它不是引用与值类型的关系。我只是使用“ Thing”的基类和两个派生类进行了尝试:“ Person”和“ Animal”。当我执行相同的操作时,我得到:“无法将类型为“动物”的对象转换为类型为“人”。因此,它肯定遍历每个元素。如果我要在列表上执行OfType,那么它将起作用。如果必须进行检查,则ForEach可能会非常慢,除非编译器对其进行了优化。
布伦特·里滕豪斯

谢谢布伦特-我当时不在那儿。foreach不过滤,但是使用更多派生类型作为迭代变量将迫使编译器尝试进行Cast,这将在第一个不符合条件的元素上失败。
StuartLC

7

您可以使用 List<Y>.ConvertAll<T>([Converter from Y to T]);


3

这并不完全是这个问题的答案,但它可能是一些有用的:如@SWeko说,幸亏协方差和逆变,List<X>不能施放List<Y>,但List<X>可以投进去IEnumerable<Y>,甚至隐式转换。

例:

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

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

最大的优点是它不会在内存中创建新列表。


1
我之所以这样,是因为如果您的源列表很大,那么一开始就不会对性能造成任何影响。取而代之的是,接收器正在处理的每个条目都有一个小的,不明显的转换。也没有巨大的内存积累。非常适合处理流。
约翰·弗朗兹(JohanFranzén),

-2
dynamic data = List<x> val;  
List<y> val2 = ((IEnumerable)data).Cast<y>().ToList();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.