8

Consider the following enum declaration and int array:

enum Test { None };

int[] data = {0};

To convert this int[] array to an Test[] array we can simply do this:

Test[] result = Array.ConvertAll(data, element => (Test)element);

I initially tried to do this:

Test[] result = data.Cast<Test>().ToArray();

However, that throws an exception at runtime:

System.ArrayTypeMismatchException: Source array type cannot be assigned to destination array type.

Question One

Is there a way to do this using Linq and Cast<> without an error, or must I just use Array.Convert()?

Question Two (added after the first question was answered correctly)

Exactly why doesn't it work?

It seems like it's a case of an implementation detail escaping... Consider:

This causes an error:

var result = data.Cast<Test>().ToList(); // Happens with "ToList()" too.

But this does not:

var result = new List<Test>();

foreach (var item in data.Cast<Test>())
    result.Add(item);

And neither does this:

var result = data.Select(x => x).Cast<Test>().ToList();

The clear implication is that some kind of optimisation is being done in the .ToList() implementation that causes this exception.


Addendum:

This also works:

List<int> data = new List<int>{0};
var result = data.Cast<Test>().ToList();

or

List<int> data = new List<int>{0};
var result = data.Cast<Test>().ToArray();

It's only if the original data is an array that it doesn't work.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276

3 Answers3

8

You can use .Select(e => (Test)e).

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
1

It doesn't work in this case because Cast first performs the following check:

public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
  IEnumerable<TResult> results = source as IEnumerable<TResult>;
  if (results != null)
    return results;
  // ... the rest of the method
}

It turns out that for int[] this check succeeds (so int[] as IEnumerable<Test> is not null). In result, Cast actually does nothing at all, it returns array as is. Then when you call ToArray, Array.Copy is invoked. It tries to copy from int[] array to Test[] array, and fails because of type mismatch. This exception you see.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • So this does actually look like a bug in the implementation to me. – Matthew Watson Dec 15 '16 at 16:47
  • @MatthewWatson not really, the reason is described here for example: http://stackoverflow.com/a/593799/5311735. In short, C# and CLR have somewhat different approaches about casting arrays of different numeric types. Here is the same story, because enum is basically just a number. You cannot cast int[] to IEnumerable (or Test[]) - compiler won't allow you because it thinks there is no such converstion. But CLR allows that and in runtime this actually happens. – Evk Dec 15 '16 at 16:52
1

Because the cast actually did not do anything since it thinks that the collection is already the desire type

    IEnumerable<Test> cast = data.Cast<Test>();
    IEnumerable<Test> select = data.Select(e => (Test) e);

    Console.WriteLine(cast == (IEnumerable<int>) data); //True
    Console.WriteLine(select == (IEnumerable<int>) data); //False
    Console.WriteLine(select == cast); //Flase

as you can see the cast IEnumerable is actually just the original array. Thus .ToArray is trying to do something similar to (Test[]) data.ToArray();

Steve
  • 11,696
  • 7
  • 43
  • 81
  • Which would be a bug in the implementation, wouldn't it? – Matthew Watson Dec 15 '16 at 16:46
  • @MatthewWatson from my point of view, yes – Steve Dec 15 '16 at 16:47
  • @matthew: depends, the Cast method is not the cast operator. Its more strict. So don't use it in this way. You are trying to convert an int[] to an Test[]. That's a conversion. There's already a meaningful and efficient conversion method in the Array class which works – Tim Schmelter Dec 15 '16 at 16:58
  • @TimSchmelter That can't be significant because it works fine if the source is a `List<>` - it only fails if the source is an array. (Turns out, this is a small bug in .Net) – Matthew Watson Dec 15 '16 at 18:01