I had following piece of code
try
{
object s = new object();
s = 10;
Console.WriteLine(\"{0}\", Convert.ToInt16(s));
Console.WriteLine(\"{0}\", (In
Your code is violating a C# language rule. Chapter 4.3.2 of the C# Language Specification says:
For an unboxing conversion to a given non-nullable-value-type to succeed at run-time, the value of the source operand must be a reference to a boxed value of that non-nullable-value-type. If the source operand is null, a System.NullReferenceException is thrown. If the source operand is a reference to an incompatible object, a System.InvalidCastException is thrown.
Perhaps not the most clearest possible language, "incompatible object" doesn't help much when talking about value types, but what it says that you can only unbox to an int. A conversion to Int16 (aka short) is not permitted. This rule is not arbitrary, it allows the jitter to generate very efficient code. It doesn't have to consider conversion so it can directly access the bits in the boxed object. Which can be done entirely inline without any helper method, it takes a dozen machine code instructions. This mattered a great deal back in .NET 1.x where generics were not available yet. So collection classes like ArrayList had to store boxed values. Getting them out of the collection required unboxing.
And yes, the Convert class is the workaround. It takes advantage of the value types implementing the IConvertible interface. So it will end up calling the Int32.ToInt16() method. Which first unboxes to int and then casts to Int16. Not as efficient but no kaboom.
A boxed value type instance during unpacking, you should note the following:
Included on the boxed value type instance is null, throws a NullReferenceException exception.
If the reference point of the object is not required a boxed value type instance throws an InvalidCastException exception.
The second means that the following code does not work properly:
s = new object();
s = 10;
Console.WriteLine("{0}", Convert.ToInt16(s));
Console.WriteLine("{0}", (Int16)s);//throws an InvalidCastException exception
The logic can obtain s referenced by a boxed Int32, and its transformation into a INT6. Unboxing operations on an object can only be transformed into unboxed value type, in this case is Int32. Here is the correct wording:
The below line solves your Issue
Console.WriteLine("{0}", (Int16)(int)s); / / first unboxing of the correct type, and then transition
In the problem line you are attempting to cast something that is not Int16 (probably Int32?) into the Int16 type. If you change the assignment to this I bet it will work (ideone):
object s, s2;
s = Convert.ToInt16(10);
s2 = 10;
Console.WriteLine("Types: {0} and {1}", s.GetType(), s2.GetType());
Console.WriteLine("s as Int16 (works): {0}", (Int16)s);
Console.WriteLine("s2 as Int16 (error): {0}", (Int16)s2);
Output:
Runtime error time: 0.03 memory: 36592 signal:-1
Types: System.Int16 and System.Int32
s as Int16 (works): 10
From the comments, casting Int32 to Int16 directly works becasue the compiler knows they can be converted and does that automatically. When you are working with a bare object though it is going blind and will not automatically convert. This will work for instance:
Console.WriteLine("s2 as Int16 (works): {0}", (Int16)((Int32)s2));
The numeric literal 10
is treated as an integer, and more specifically an Int32
. Though you typed your variable as object
, under the covers it is still the integer. You can only unbox a value type to its same type, or to a nullable version of that type, directly.
For example, this code:
int i = 10;
object o = i;
short j = (short)o;
Will not execute, because the original value of i
is not a short, it is an integer. You have to first unbox to integer, then you can cast to short.
short j = (short)(int)o;
Convert.ToInt16
sidesteps that issue, and the way it does it is an implementation detail. However, that method has multiple overloads that accepts multiple types, including strings, so it is not the equivalent of code using a direct cast.
Edit: I noticed I'm mixing terms here, so just so it's clear for a novice C# reader, the names short
and Int16
are interchangeable for a 16 bit integer, as are the names int
and Int32
for 32 bit integers. In C#, short
and int
are aliases for the .NET types Int16
and Int32
, respectively.
It depends what "s" is in your application. When you call Convert.ToInt16(s), you safely attempt to cast whatever object s is to an Int16. If "s" is previously declared as an object (object s), then it can unbox the 10 from the object. But because it is an object, you cannot explicitly cast it to an Int16, because after all, you did declare it as an object. See the code below:
object s;
s = new object();
s = 10;
Int16 newInt = (Int16)s; // This will throw, because s is actually an object
Int16 newInt = Convert.ToInt16(s); // This will not fail, because the convert is actually holding a value of 10.