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

Jim*_*mbo 216 c# ienumerable casting list

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

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

foreach(X x in ListOfX)
    ListOfY.Add((Y)x);
Run Code Online (Sandbox Code Playgroud)

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

ListOfY = (List<Y>)ListOfX;
Run Code Online (Sandbox Code Playgroud)

Jam*_*iec 452

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

List<Y> listOfY = listOfX.Cast<Y>().ToList();
Run Code Online (Sandbox Code Playgroud)

有些事情需要注意(对评论者的H/T!)

  • 还有另一枚金徽章.这非常有用. (12认同)
  • 另请注意,虽然这会对列表中的每个项目进行转换,但列表本身并不会被转换; 而是使用所需类型创建新列表. (7认同)
  • 必须包含以下行才能使编译器识别这些扩展方法:using System.Linq; (5认同)
  • 还要注意`Cast <T>`方法不支持自定义转换运算符.[为什么Linq Cast助手不能与隐式演员一起使用](http://stackoverflow.com/questions/14523530/why-does-the-linq-cast-helper-not-work-with-the-implicit-投运营商). (4认同)
  • 顺便说一句,我不得不谷歌 H/T。我以前从没见过这样的事 (2认同)

SWe*_*eko 91

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

虽然能够编写这样的代码似乎很正常:

List<Animal> animals = (List<Animal>) mammalList;
Run Code Online (Sandbox Code Playgroud)

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

List<Mammal> mammals = (List<Mammal>) animalList;
Run Code Online (Sandbox Code Playgroud)

因为不是每一种动物都是哺乳动物.

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

IEnumerable<Animal> animals = mammalList.Cast<Animal>();
Run Code Online (Sandbox Code Playgroud)

这简化了铸件.这在语法上等同于你的逐个添加代码,因为它使用显式强制转换将Mammal列表中的每个强制转换为a 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
Run Code Online (Sandbox Code Playgroud)

  • @Jamiec我没有+1,因为他以"不,这是不可能的"开头,而埋没答案的人很多都在寻找这个问题.从技术上讲,他确实更彻底地回答了OP的问题. (6认同)
  • 我无法相信,直到现在为止,香港专业教育学院从未对此答案+1。它比我上面的要好得多。 (2认同)

Stu*_*tLC 10

添加到Sweko的观点:

施法之所以如此

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

是不可能的,因为List<T>在类型T中不变的,因此无论是否X从中派生出来Y- 这是因为List<T>定义为:

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T> ... // Other interfaces
Run Code Online (Sandbox Code Playgroud)

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

但是,如果您的设计中不需要可变集合,则可以对许多不可变集合进行向上转换,例如,如果Giraffe派生自以下内容Animal:

IEnumerable<Animal> animals = giraffes;
Run Code Online (Sandbox Code Playgroud)

这是因为IEnumerable<T>支持协方差T- 这是有道理的,因为它IEnumerable意味着集合无法更改,因为它不支持从集合中添加或删除元素的方法.请注意out声明中的关键字IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable
Run Code Online (Sandbox Code Playgroud)

(这里进一步解释了为什么可变集合List不能支持covariance,而不可变迭代器和集合可以.)

铸造 .Cast<T>()

正如其他人所提到的,.Cast<T>()可以应用于集合来投射一个新的元素集合,这些集合被投射到T,但是这样做会抛出一个InvalidCastException如果不能在一个或多个元素上强制转换(这与执行显式的行为相同)在OP的foreach循环中投射).

过滤和铸造 OfType<T>()

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

的foreach

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

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

foreach(Y y in ListOfX)
{
    ListOfY.Add(y);
}
Run Code Online (Sandbox Code Playgroud)

也将尝试铸造.但是,如果不能进行强制转换,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) { }
}
Run Code Online (Sandbox Code Playgroud)

使用混合类型的集合时:

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();
Run Code Online (Sandbox Code Playgroud)

鉴于:

var castedAnimals = mixedAnimals.OfType<Elephant>()
    .ToList();
// Ellie
Run Code Online (Sandbox Code Playgroud)

只过滤掉大象 - 即消除了斑马.

Re:隐式投射算子

如果没有动态,用户定义的转换运算符仅在编译时*使用,因此即使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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

var compilerInferredAnimals = new []
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};
Run Code Online (Sandbox Code Playgroud)

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

*正如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
Run Code Online (Sandbox Code Playgroud)


And*_*rey 7

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


Xav*_*987 6

这不完全是这个问题的答案,但它可能对某些人有用:正如@SWeko 所说,由于协变和逆变,List<X>不能强制转换为,List<Y>List<X>可以转换为IEnumerable<Y>,甚至可以使用隐式转换。

例子:

List<Y> ListOfY = new List<Y>();
List<X> ListOfX = (List<X>)ListOfY; // Compile error
Run Code Online (Sandbox Code Playgroud)

List<Y> ListOfY = new List<Y>();
IEnumerable<X> EnumerableOfX = ListOfY;  // No issue
Run Code Online (Sandbox Code Playgroud)

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