palapapa
palapapa

Reputation: 959

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

Suppose I have 2 classes, A and B, and B can be cast to A. I declared an array of type B[] called b. Then if I wanted to cast b to A[], what's the difference between (A[])b and b.Cast<A>()?

Upvotes: 4

Views: 955

Answers (3)

user12031933
user12031933

Reputation:

These are two different things.

Language casting

(A[])b cast b to type A[] and does not compile or throws an exception at runtime if b is not type of A[].

Take for example the case of doubles and integers:

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

Here we just cast a type to another, no matter what they are, collection or not.

Casting and type conversions (C# Programming Guide)

Linq Cast

Cast<TResult> convert each items of an IEnumerable to TResult.

It's just a LINQ loop already written to make our life easier over boxed values.

Enumerable.Cast(IEnumerable) Method

Casts the elements of an IEnumerable to the specified type.

From the source code

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

Thus this method can be used to unbox boxed values from a collection like Rows of a DataGridView or any similar "reduced" collections like for example Items in a ListBox or a ComboBox.

That means that the type of the items must be type of TResult or ancestor.

Example

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 

Note

Because of the yielding, Cast method is deferred, so we get the result only when it is executed, for example using foreach or ToList.

Deferred Execution of LINQ Query

Deferred Vs Immediate Query Execution in LINQ

Deferred execution and lazy evaluation

Alternative to solve the problem on the sample

Therefore to convert the array, we can use a direct cast using for example a foreach or Select:

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

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

> 10
> 20

Using an extension method

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

Also CultureInfo.InvariantCulture to avoid problems on numbers, as well as a formatter to avoid roundings.

Upvotes: 3

pinkfloydx33
pinkfloydx33

Reputation: 12799

Your two examples, while different, are both invalid.

You cannot cast an array of one object type to another, even if there exists a conversion operator between them (explicit or implicit). The compiler rightly prevents such a cast. The exception to this rule is if there exists an inheritance relationship; thanks to array covariance you can downcast to a base type (for reference types). The following works:

class A {} 
class B : A {} 

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

See SharpLab

The same principles hold true for the Cast<T> method in LINQ--unless the types match, an exception will be thrown at runtime upon enumeration. The answer below is incorrect. You cannot, for example, Cast an array of double to int. Of course, if you don't enumerate the result (such as in the example) then no exception occurs. However upon actually enumerating (foreach, ToList, ToArray) an InvalidCastException will be thrown.

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 

Notice the temp variable--as in the answer below it doesn't throw thanks to LINQ's deferred execution. It's once you enumerate it that it fails. See SharpLab.

The Cast method was designed to bridge the gap with pre-generic collections where values were stored internally as an array of object and the collections themselves only implement IEnumerable. Cast allows one to convert to IEnumerable<T>, however no casting/converting other than from object to the original type is allowed.

For structs this is obvious--a boxed double can only be unboxed to a double; it cannot be unboxed to an int. Take the simple, non-array case:

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

See SharpLab

It makes sense then, that Cast<int> will fail as the method only inserts the single cast to int, and not the intermediate cast to double that would otherwise be required.

For classes, again Cast will only insert the direct cast. The method is generic and does not/cannot account for any user defined operators. So when you say you "have two classes that can be cast to each other" this still would not matter. In other words, the following will fail:

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 

See SharpLab

Again (as above), the exception to this rule is if there exists an inheritance relationship between the two classes. You can downcast from one to the other:

class A {} 
class B : A {} 

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

See SharpLab

One alternative is to use LINQ's Select to project your original collection, applying the conversion operators you desire:

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! 

See SharpLab. This would also work in the case of the double/int:

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

See SharpLab

Upvotes: 2

Daniel A. White
Daniel A. White

Reputation: 191037

.Cast<T> is from Linq. It will enumerate the collection casting each item to T and creating a new sequent. The other is an explicit cast, telling the compiler you would want to access the original as that type.

Upvotes: 2

Related Questions