Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cast<int>.Cast<int?> applied on generic enum collection results in invalid cast exception

enum Gender { Male, Female }

var k = new[] { Gender.Male }.Cast<int>().ToList().Cast<int?>().ToList(); //alright

var p = new[] { Gender.Male }.Cast<int>().Cast<int?>().ToList(); //InvalidCastException

What is the cause for the second case? I know I cant cast a boxed enum to int? directly, but I do a two stage casting, ie Cast<int>.Cast<int?> which should be working.

Edit:

This is surprising considering the below works:

object o = Gender.Male;
int i = (int)o; // so here the cast is not to an entirely different type, which works
like image 579
nawfal Avatar asked Nov 29 '13 19:11

nawfal


2 Answers

Ok I have come to find out the cause, which is still strange. I should have checked up Cast<T> implementation myself first of all!

This is how Cast<T> is implemented:

public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
    IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
    if (enumerable != null)
    {
        return enumerable; // this is the culprit..
    }
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return Enumerable.CastIterator<TResult>(source);
}

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

Now the problem is with the first Cast<int> call:

new[] { Gender.Male }.Cast<int>()

Here source as IEnumerable<TResult> where source is new[] { Gender.Male } and TResult is int in the generic method returns a non null value (which basically means (new[] { Gender.Male } is an IEnumerable<int> in generic context), and hence it returns the same enumerable back which is Gender[], and in the next Cast<int?> call, the actual cast is performed, which is from Gender to int? which fails. As to why this happens in generic context, catch it in this question.

like image 100
nawfal Avatar answered Nov 04 '22 00:11

nawfal


This is due to deferred execution. The latter statement actually tries to cast Gender.Male to int?. In contrast, the first statement actually performs the cast operation to an int and gets a List<int> before deferring the execution to cast those to int?; clearly int to int? has an implicity conversion defined already.

like image 1
Mike Perrenoud Avatar answered Nov 03 '22 23:11

Mike Perrenoud