我知道将一个类型的项目列表转换为另一个类型是可能的(假设你的对象有一个公共静态显式操作符方法来进行转换),可以按照以下方式逐个进行转换:
List<Y> ListOfY = new List<Y>();
foreach(X x in ListOfX)
ListOfY.Add((Y)x);
但是难道不可能一次性将整个列表转换类型吗?例如,
ListOfY = (List<Y>)ListOfX;
我知道将一个类型的项目列表转换为另一个类型是可能的(假设你的对象有一个公共静态显式操作符方法来进行转换),可以按照以下方式逐个进行转换:
List<Y> ListOfY = new List<Y>();
foreach(X x in ListOfX)
ListOfY.Add((Y)x);
但是难道不可能一次性将整个列表转换类型吗?例如,
ListOfY = (List<Y>)ListOfX;
如果X
确实可以转换为Y
,那么您应该能够使用
List<Y> listOfY = listOfX.Cast<Y>().ToList();
需要注意的一些事情(感谢评论者!)
using System.Linq;
才能获取此扩展方法。ToList()
将创建一个新的List<Y>
。直接强制类型转换 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
,如果转换失败会导致失败。
如果您需要更多对类型转换过程的控制,可以使用List<T>
类的ConvertAll
方法,该方法可以使用提供的表达式来转换列表项。 它还有一个额外的好处,就是返回一个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
继续说 Sweko 的观点:
这就是为什么要进行强制类型转换的原因
var listOfX = new List<X>();
ListOf<Y> ys = (List<Y>)listOfX; // Compile error: Cannot implicitly cast X to Y
List<T>
在类型 T 上是不变的,因此无论 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>
支持在 T
上的协变 - 这很有意义,因为 IEnumerable
意味着集合不能被更改,因为它没有支持添加或删除元素的方法。请注意在 IEnumerable<T>
声明中的 out
关键字:
public interface IEnumerable<out T> : IEnumerable
(这里有进一步的解释,为什么可变集合如List
不支持covariance
,而不可变迭代器和集合可以。)
使用.Cast<T>()
进行强制转换
正如其他人所提到的那样,可以将.Cast<T>()
应用于集合以投影出一个新的元素类型为T的集合,但是如果其中一个或多个元素的强制转换不可能,则会抛出InvalidCastException
(这与在OP的foreach
循环中进行显式转换的行为相同)。
使用OfType<T>()
进行筛选和强制转换
如果输入列表包含不同或不兼容类型的元素,则可以使用 .OfType<T>()
代替 .Cast<T>()
来避免潜在的 InvalidCastException
. (.OfType<>()
在尝试转换之前检查元素是否可以转换为目标类型,并过滤掉不兼容的类型。)
还要注意,如果 OP 改成这样写: (请注意 foreach
中的显式 Y y
)
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
筛选出只有大象 - 即斑马被消除。
关于隐式转换运算符
没有动态性,用户定义的转换运算符仅在编译时*使用,因此即使提供了斑马和大象之间的转换运算符,上述转换方法的运行时行为也不会改变。
如果我们添加一个将斑马转换为大象的转换运算符:
public class Zebra : Animal
{
public Zebra(string name) : base(name) { }
public static implicit operator Elephant(Zebra z)
{
return new Elephant(z.Name);
}
}
相反,鉴于上述的转换运算符,编译器将能够将下面的数组从Animal[]
类型更改为Elephant[]
类型,因为现在斑马可以被转换为一个同质的大象集合:
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
不会过滤,但是使用更具体的类型作为迭代变量将强制编译器尝试进行转换,这将在第一个不符合条件的元素上失败。 - StuartLCList<Y>.ConvertAll<T>([将Y转换为T的转换器]);
。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
最大的优势是它不会在内存中创建新的列表。
ToList<T>
方法代替 Cast<T>
。listOfX.ToList<Y>()
dynamic data = List<x> val;
List<y> val2 = ((IEnumerable)data).Cast<y>().ToList();