【问题标题】:What is the difference between directly casting an array or using System.Linq.Cast?直接转换数组或使用 System.Linq.Cast 有什么区别?
【发布时间】:2021-08-29 09:44:57
【问题描述】:

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

【问题讨论】:

  • 第一个会给你一个数组,第二个是IEnumerable<A>

标签: c# arrays linq casting enumerable


【解决方案1】:

这是两个不同的东西。

语言转换

(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 循环,以使我们的生活比 盒装 值更轻松。

Enumerable.Cast(IEnumerable) Method

将 IEnumerable 的元素转换为指定类型。

来自source code

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

因此,此方法可用于从 RowsDataGridView 之类的集合或任何类似“简化”集合(例如 ListBoxComboBox 中的 Items 之类的集合中拆箱值。

这意味着项目的类型必须是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 以避免数字问题,以及避免四舍五入的格式化程序。

【讨论】:

  • 所提供的扩展方法仅适用于基本类型和/或实现IConvertible 的类型(Convert.ChangeType 需要)。对于 OP 声称他们有“两个班级”的说法,这种方法不太可能奏效。如果他们的类没有实现IConvertible,那么ChangeType 将抛出(除非目标类型与源类型相同)......如果他们确实通过了实现IConvertible 的体操(所有17 种方法!! ) 他们必须对IConvertible.ToType 进行特殊处理,以说明他们希望支持转换为 的每种可能类型。不好玩!
【解决方案2】:

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

【讨论】:

  • 那么通过使用Cast&lt;T&gt; 可以在运行时确定要转换为的类型,而直接转换则不能?
  • Cast&lt;&gt;,尽管它的名称不执行任何隐式或显式转换。它适用于所有内容都存储为object 的预通用集合。它会将 object 中的项目转换为指定的类型。但是,在枚举时,除非值是 完全相同的类型(对于结构)或具有 继承关系(对于类),“强制转换”将失败并抛出 @987654327 @
【解决方案3】:

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

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

class A {} 
class B : A {} 

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

SharpLab

同样的原则也适用于 LINQ 中的 Cast&lt;T&gt; 方法——除非类型匹配,否则将在运行时枚举时抛出异常。下面的答案是不正确的。例如,您不能将 Cast 一个数组 doubleint。当然,如果不枚举结果(如示例中),则不会发生异常。然而,在实际枚举 (foreach, ToList, ToArray) 时,将抛出 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&lt;T&gt;,但是除了从 object 到原始类型之外,不允许转换/转换。

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

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

SharpLab

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

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

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

【讨论】:

  • 是不是因为在Cast的源代码中,它像foreach (object obj in source) yield return (TResult)obj;一样迭代原始集合中的元素,所以即使有转换运算符,它仍然会失败,因为元素正在被迭代作为objects?
  • 没有。这是因为泛型演员(TResult)obj 是... generic。它不知道用户定义的演员表。强制转换是下面的方法,需要编译器在强制转换站点插入对它们的调用。但由于它是一种通用方法,编译器无法知道它实际上是什么。该方法的底层代码不会因为泛型参数而神奇地改变,那么如何插入对任意 TResult 的适当静态方法调用呢?不能。
猜你喜欢
  • 1970-01-01
  • 2018-07-28
  • 1970-01-01
  • 2018-05-26
  • 1970-01-01
  • 2013-05-12
  • 2014-12-04
  • 1970-01-01
  • 2012-06-02
相关资源
最近更新 更多