Differences between using the ‘Convert’ class vs. casting (Part 1 of 2)

In the previous post "Why does SubSonic’s SimpleRepository Add<T> Method return a decimal instead of an int?" I talked some about what appeared to be some odd behavior regarding the return value of the ‘Add<T>’ method in SubSonic’s SimpleRepository. In a nutshell, I expected the object that this method returned to have an underlying type of ‘int’ since the type that it was dealing with had an int as the primary key / identity field. Instead, the method was returning an object with an underlying type of ‘decimal’ and my cast to an int was failing.

Long story short, I determined one course of action would be to use the Convert.ToInt32() on the return value to safely convert it to an int. This raised a question: why did Convert.ToInt32() work but a simple cast wouldn’t?

Let’s boil it down to some simple examples. This code compiles and runs with no errors:

decimal decNumber = 2.2M;
int intNumber = (int)decNumber;
Console.WriteLine("intNumber = {0}", intNumber);

The ‘WriteLine” call will show that the ‘intNumber’ variable equals 2. So it’s OK to cast from a decimal to an int, even if it results in a loss of precision. The above code, however, doesn’t accurately reproduce the scenario we were having with the ‘Add<T>’ method. This method doesn’t return a decimal, it returns an object with an underlying type of decimal. The following code more accurately shows the cast that I was originally trying to do:

decimal decNumber = 2.2M;
object number = decNumber;
int intNumber = (int)number;
Console.WriteLine("intNumber = {0}", intNumber);

This code compiles fine, but will result in an ‘InvalidCastException’ at runtime. So if directly casting from an decimal to an int is allowed (as illustrated in the first snippet) why can’t I cast an object with an underlying type of decimal to an int?

The answer can be found in the CSharp Language Specifications section 4.3:

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.

We’re only allowed to unbox an object to a variable of the same type that was originally boxed. That makes sense, so how do we get the decimal in an object’s clothing returned by ‘Add<T>’ into an integer variable?

If we make one small change to the previous code snippet and use ‘Convert.ToInt32’ instead of the ‘(int)’ cast, the runtime error goes away and we get the same output as our first code snippet from above.

decimal decNumber = 2.2M;
object number = decNumber;
int intNumber = Convert.ToInt32(number);
Console.WriteLine("intNumber = {0}", intNumber);

This works and solves the immediate problem that we were trying to solve, but I couldn’t help but wonder why. Why does Convert.ToInt32 work here but (int) doesn’t?

We’ll break out Reflector and take a closer look in the next post.