Puzzling Enumerable.Cast Invalidcastexception

Puzzling Enumerable.Cast InvalidCastException

That's very odd! There's a blog post here that describes how the behaviour of Cast<T>() was changed between .NET 3.5 and .NET 3.5 SP1, but it still doesn't explain the InvalidCastException, which you even get if you rewrite your code thus:

var list = new[] { 1 };
var castedList = from long l in list select l;
Console.WriteLine(castedList.First());

Obviously you can work around it by doing the cast yourself

var castedList = list.Select(i => (long)i);

This works, but it doesn't explain the error in the first place. I tried casting the list to short and float and those threw the same exception.

Edit

That blog post does explain why it doesn't work!

Cast<T>() is an extension method on IEnumerable rather than IEnumerable<T>. That means that by the time each value gets to the point where it's being cast, it has already been boxed back into a System.Object. In essence it's trying to do this:

int i = 1;
object o = i;
long l = (long)o;

This code throws the InvalidCastException you're getting. If you try to cast an int directly to a long you're fine, but casting a boxed int back to a long doesn't work.

Certainly an oddity!

Why Enumerable.Cast raises an InvalidCastException?

Well, you have incorrect expectations of Cast, that's all - it's meant to deal with boxing/unboxing, reference and identity conversions, and that's all. It's unfortunate that the documentation isn't as clear as it might be :(

The solution is to use Select:

doubleNumbers2 = intNumbers.Select(x => (double) x).ToArray();

Enumerable.Cast T extension method fails to cast from int to long, why?

The relevant line in Cast:

 this.<>2__current = (TResult)this.<obj>5__ab;

We can mimic this using the following code:

int foo = 1;
long bar = Cast<long>(foo); //oh noes!

T Cast<T>(object input)
{
return (T)input;
}

Which also fails. The key here is that at the point of cast, it's an object. Not an int. This fails because we can only unbox from an object to the exact type we want. We are going from object - which could be a boxed long, but it's not. It's a boxed int. Eric Lippert discussed this on his blog:

we’ve decided that unboxing can only unbox to the exact type. If you want to call the slow method that does all that goo, it’s available – you can always call Convert...

In your code which works, you're not dealing with a boxed int (an object), you've got an int.

Why doesn't Enumerable.Cast use my conversion operator?

Read Jon Skeet's blog about reimplementing Linq (EduLinq), specifically part 33, where he says this:

It's worth noting that (as of .NET 3.5 SP1) Cast and OfType only perform reference and unboxing conversions. They won't convert a boxed int to a long, or execute user-defined conversions. Basically they follow the same rules as converting from object to a generic type parameter. (That's very convenient for the implementation!)

IEnumerable.Cast not calling cast overload

The Cast<T> function works on IEnumerable, not IEnumerable<T>. As such, it's treating the instances as System.Object, not as your specific type. The explicit conversion does not exist on object, so it fails.

In order to do you method, you should use Select() instead:

Class2 c3 = c1.Select(c => (Class2)c).First();

Cast method of LINQ behaves unexpected

Enumerable.Cast only performs unboxing and reference conversions. It does not perform other conversions such as the built-in value type ones and user-defined conversions.

Before .NET 3.5 SP1 it actually did perform rather more conversions for you. I'm sure the person who performed the code review blogged about it, but I can never remember who it was. (I keep thinking it was Eric Lippert, but it wasn't.) I'll look it up.

One thing I'll say is that this could be better documented.

As it happens, this is the first puzzler in Bill Wagner's recent video.



Related Topics



Leave a reply



Submit