Why are the rules that govern whether it's possible to type cast between signed and unsigned integer types so inconsistent?

In C#, the ability to cast between signed and unsigned integer types appears to be influenced by:

  1. Whether a scalar or an array type is being converted.
  2. Whether the variable is declared as an object.

Consider the following code example:

// If the variable is declared as a byte array then type casting to sbyte[] results in a 
// compile-time error.
byte[] byteArray = new byte[2];
var c = (sbyte[])byteArray; // Compilation eror

// But if the variable is declared as an object then we neither get a compile-time nor a 
// run-time error
object byteArrayObject = new byte[2];
var a = (sbyte[])byteArrayObject;

// With an explicitly typed scalar, the byte -> sbyte type conversion succeeds with no errors
byte scalarByte = 255;
var b = (sbyte)scalarByte;

// But if the scalar is declared as an object, an InvalidCastException is thrown at run-time
object byteObject = (byte)4;
var e = (sbyte)byteObject; // InvalidCastException

To summarize:

While this example only considers bytes, the same pattern appears to hold true for other integer types. Can anyone explain why these results are so inconsistent?

Upvotes: 4

Views: 186

Answers (1)

InBetween
InBetween

Reputation: 32770

The second case has nothing to do with signed or unsigned types. You simply can not unbox a value type to something that is not its exact type. This will also fail:

object i = 1;
var l = (long)i; //Runtime expection: unboxing an int to a long

The first case is an unfortunate mismatch between allowed conversiones in C# and the CLR; C# disallows value type array variance (again, it's more general than signed and unsigned types). When casting from object the compiler can't disallow it because it simply doesn't have enough information to do so, and the CLR then succeeds.

Do note though, that this only happens with value type arrays. Reference type arrays are variant in C# (unfortunately):

var strs = new string[];
var objs = (object[])strs; //Compiles just fine.

This is unfortunate because it's a broken variance; nobody stops you from doing this:

objs[0] = new object(); //Runtime exception, an object is not a string. Ouch!

Also of interest is that c# generic type variance in interfaces and delegates doesn't work with value types either. A good explanation of why this is so can be found here: here

Upvotes: 2

Related Questions