Generics: casting and value types, why is this illegal?

Why is this a compile time error?

public TCastTo CastMe<TSource, TCastTo>(TSource i)
{
     return (TCastTo)i;
}

Error:

Cannot convert type ‘TSource’ to ‘TCastTo’

And why is this a runtime error?

public TCastTo CastMe<TSource, TCastTo>(TSource i)
{
     return (TCastTo)(object)i;
}

int a = 4;
long b = CastMe<int, long>(a); // InvalidCastException

// this contrived example works
int aa = 4;
int bb = CastMe<int, int>(aa);

// this also works, the problem is limited to value types
string s = "foo";
object o = CastMe<string, object>(s);

I’ve searched SO and the internet for an answer to this and found lots of explanations on similar generic related casting issues, but I can’t find anything on this particular simple case.

Answer

Why is this a compile time error?

The problem is that every possible combination of value types has different rules for what a cast means. Casting a 64 bit double to a 16 bit int is completely different code from casting a decimal to a float, and so on. The number of possibilities is enormous. So think like the compiler. What code is the compiler supposed to generate for your program?

The compiler would have to generate code that starts the compiler again at runtime, does a fresh analysis of the types, and dynamically emits the appropriate code.

That seems like perhaps more work and less performance than you expected to get with generics, so we simply outlaw it. If what you really want is for the compiler to start up again and do an analysis of the types, use “dynamic” in C# 4; that’s what it does.

And why is this a runtime error?

Same reason.

A boxed int may only be unboxed to int (or int?), for the same reason as above; if the CLR tried to do every possible conversion from a boxed value type to every other possible value type then essentially it has to run a compiler again at runtime. That would be unexpectedly slow.

So why is it not an error for reference types?

Because every reference type conversion is the same as every other reference type conversion: you interrogate the object to see if it is derived from or identical to the desired type. If it’s not, you throw an exception (if doing a cast) or result in null/false (if using the “as/is” operators). The rules are consistent for reference types in a way that they are not for value types. Remember reference types know their own type. Value types do not; with value types, the variable doing the storage is the only thing that knows the type semantics that apply to those bits. Value types contain their values and no additional information. Reference types contain their values plus lots of extra data.

For more information see my article on the subject:

http://ericlippert.com/2009/03/03/representation-and-identity/

Attribution
Source : Link , Question Author : Matt Greer , Answer Author : Eric Lippert

Leave a Comment