Implicit and explicit numeric casts are usually pretty straightforward:
- int i = 5;
- float f = i;
- Byte b = (Byte) i;
If you’re not paying attention, a safe explicit numeric cast can throw a runtime exception:
- Object i = 5;
- float l = (float) i;
- ArrayList a = new ArrayList();
- int b;
- for (int i = 0; i < 10; i++) {
- b = 10;
- a.Add(b);
- }
- float f = (float) a[0];
Why is a cast from int to float ok, but a cast from int ArrayList[i] to float not ok?
Type Casting vs Numeric Casting
Our InvalidCastException is not coming from our Explicit Cast from int to float. Our exception is coming from our Unboxing an int to float.
A key difference between Value Types and Reference Types is where they get allocated. Value Types are allocated in the current Thread Stack. Reference Types are allocated in the Managed Heap (think garbage collection).
Boxing is the act of casting a Value Type to a Reference Type.
We are Boxing when we do things like:
- Assign value types to reference type fields.
- Add value types to a collection reference type.
- Pass a value type as a parameter to a method that takes a reference type.
Unboxing is the act of casting a Reference Type to a Value Type.
We are Unboxing when we do things like:
- Extract a value type from a reference type.
- Extract a value type from a collection of value types.
- Pass a boxed value type to a method that takes a value type.
Unboxing’s Constraints
During Unboxing, two constraints get checked by the CLR:
- If the variable containing the reference to the boxed value type instance is null, a NullReferenceException is thrown.
- If the reference doesn’t refer to an object that is a boxed instance of the desired value type, an InvalidCastException is thrown.
Unboxing to a Value Type that’s different from the originally Boxed Value Type is not allowed.
- Object i = 5;
- float l = (float)i;
- ArrayList a = new ArrayList();
- int b;
- for (int i = 0; i < 10; i++)
- {
- b = 10;
- a.Add(b);
- }
- float f = (float) a[0];
So now that we understand the problem, what’s the solution?
System.Convert Namespace
If you look at the .NET Reference Source for System.Int32, you’ll notice that it implements IConvertible.
- public struct Int32 :
- IComparable,
- IFormattable,
- IConvertible,
- IComparable<Int32>,
- IEquatable<Int32>
Value Types like System.Int32 that implement IConvertible use the static System.Convert to do our Unboxing and Explicit Casting.
Here’s the definition of System.Convert.ToSingle from the .NET Reference Source:
-
- float IConvertible.ToSingle(IFormatProvider provider)
- {
- return Convert.ToSingle(m_value);
- }
- Our corrected examples:
-
- Object o = 5;
- float f = Convert.ToSingle(o);
-
- ArrayList a = new ArrayList();
- int b;
- for (int i = 0; i < 10; i++)
- {
- b = 10;
- a.Add(b);
- }
- Convert.ToSingle(a[0]);
Summary
We ran into a runtime exception when trying to do an explicit numeric cast that is usually successful.
The real cause of the issue is the way that we were casting from a Value Type to a Reference Type and then back to a Value Type. This is called Boxing and Unboxing. One of the rules of Unboxing is that we can only Unbox into the same Value Type we started with. Otherwise, we throw an InvalidCastException at runtime.
Our solution is to take advantage of the System.Convert static class. It provides us with a .ToXXX method that can be called for Value Types that implement IConvertible.
Hope this finds its way to you if/when you run into this problem, or it helps you stay aware of what the CLR is doing behind the scenes. As always, feel free to leave a comment below.