直接投数组和使用System.Linq.Cast有什么区别?

What is the difference between directly casting an array or using System.Linq.Cast?

假设我有 2 个 类、AB,并且 B 可以转换为 A。我声明了一个名为 bB[] 类型的数组。那如果我想把b转换成A[](A[])bb.Cast<A>()有什么区别?

.Cast<T> 来自 Linq。它将枚举将每个项目转换为 T 的集合并创建一个新的序列。另一个是显式转换,告诉编译器您希望访问原始类型。

这是两个不同的东西。

语言转换

(A[])bb 转换为类型 A[],如果 b 不是 A[] 的类型,则不会编译或在运行时抛出异常。

以双精度和整数为例:

var array = new object[2];

array[0] = 10.2;
array[1] = 20.8;

var casted = (int[])array; // does not compile here,
                           // or throw an exception at runtime if types mismatch

这里我们只是将一种类型转换为另一种类型,不管它们是什么,集合与否。

Casting and type conversions (C# Programming Guide)

Linq Cast

Cast<TResult>IEnumerable 的每个项目转换为 TResult

这只是一个已经编写好的 LINQ 循环,让我们的生活更轻松 boxed 值。

Enumerable.Cast(IEnumerable) Method

Casts the elements of an IEnumerable to the specified type.

来自source code

static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source)
{
  foreach (object obj in source) yield return (TResult)obj;
}

因此,此方法可用于从 DataGridView 中的 Rows 或任何类似的“简化”集合(例如 Items 中的 [=] 中取消装箱值26=] 或 ComboBox.

这意味着项目的类型必须是 TResult 或祖先的类型。

例子

var array = new object[2];

array[0] = 10.2;
array[1] = 20.8;

var converted = array.Cast<int>(); // compiles but will not work
                                   // and throw an InvalidCastException 

备注

因为yield,Cast方法被延迟了,所以我们只有在执行时才得到结果,例如使用foreachToList.

Deferred Execution of LINQ Query

Deferred Vs Immediate Query Execution in LINQ

Deferred execution and lazy evaluation

替代解决样本上的问题

因此要转换数组,我们可以使用直接转换,例如使用 foreachSelect:

var converted = array.Select(v => (int)v).ToArray(); // get int[]

Console.WriteLine(string.Join(Environment.NewLine, converted));

> 10
> 20

使用扩展方法

static public class EnumerableHelper
{
  static public IEnumerable<TResult> Cast<TSource, TResult>(this IEnumerable<TSource> source)
    where TSource : IConvertible
  {
    foreach ( TSource obj in source )
      yield return (TResult)Convert.ChangeType(obj, typeof(TResult));
  }
}

var converted = array.Cast<double, int>();

> 10
> 21

还有 CultureInfo.InvariantCulture 来避免数字问题,还有一个格式化程序来避免四舍五入。

你的两个例子虽然不同,但都是无效的。

您不能将一种对象类型的数组转换为另一种对象类型,即使它们之间存在转换运算符(显式或隐式)。编译器正确地阻止了这样的转换。该规则的例外情况是是否存在继承关系;由于数组协变,您可以向下转换为基本类型(对于引用类型)。以下作品:

class A {} 
class B : A {} 

B[] bs = new[] { new B() };
A[] result = (A[])bs; // valid

SharpLab

同样的原则也适用于 LINQ 中的 Cast<T> 方法——除非类型匹配,否则将在运行时 根据枚举 抛出异常。下面的答案是不正确的。例如,您不能 Cast doubleint 的数组。当然,如果你不枚举结果(如例子中)则不会发生异常。但是,在实际枚举 (foreachToListToArray) 时,将抛出 InvalidCastException

var array = new double[2];

array[0] = 10;
array[1] = 20;

var temp = array.Cast<int>(); // OK, not enumerated 
var converted = temp.ToList(); // bam! InvalidCastException 

注意 temp 变量——因为在下面的答案中,由于 LINQ 的延迟执行,它不会抛出。一旦你枚举它,它就会失败。参见 SharpLab

Cast 方法旨在弥合与前泛型集合之间的差距,在前泛型集合中,值在内部存储为 object 的数组,而集合本身仅实现 IEnumerableCast 允许转换为 IEnumerable<T>,但是除了从 object 到原始类型之外,不允许 casting/converting。

对于结构,这是显而易见的——装箱的 double 只能拆箱为 double;它无法拆箱为 int。以简单的非数组情况为例:

double d = 1.5;
object o = d;
int iOk = (int)(double)o; // ok
int iBad = (int)o; // fails

SharpLab

那么,Cast<int> 将失败是有道理的,因为该方法仅将单个转换插入到 int,而 而不是 将中间转换插入到 double否则将需要。

对于类,再次Cast将只插入直接转换。该方法是通用的,并且 not/cannot 考虑到任何用户定义的运算符。因此,当您说“有两个 类 可以相互转换”时,这仍然无关紧要。换句话说,以下将失败:

class A {} 
class B {
    public static implicit operator A(B b) => new A();
} 

B[] bs = new[] { new B() };
var temp = bs.Cast<A>(); // OK, not yet enumerated
A[] result = temp.ToArray(); // throws InvalidCastException 

SharpLab

同样(如上所述),此规则的例外情况是两者之间存在继承关系 类。你可以从一个人沮丧到另一个人:

class A {} 
class B : A {} 

B[] bs = new[] { new B() };
A[] result = bs.Cast<A>().ToArray(); // valid

SharpLab

一种替代方法是使用 LINQ 的 Select 来投影您的原始集合,应用您想要的转换运算符:

class A {} 
class B {
    public static implicit operator A(B b) => new A();
} 

B[] bs = new[] { new B() };
A[] result = bs.Select(b => (A)b).ToArray(); // valid! 

参见 SharpLab。这也适用于 double/int:

var array = new double[] { 10.2, 20.4 };
int[] result = array.Select(d => (int)d).ToArray();

SharpLab