64

I have an IDataRecord reader that I'm retrieving a decimal from as follows:

decimal d = (decimal)reader[0];

For some reason this throws an invalid cast exception saying that the "Specified cast is not valid."

When I do reader[0].GetType() it tells me that it is an Int32. As far as I know, this shouldn't be a problem....

I've tested this out by this snippet which works just fine.

int i = 3750;
decimal d = (decimal)i;

This has left me scratching my head wondering why it is failing to unbox the int contained in the reader as a decimal.

Does anyone know why this might be occurring? Is there something subtle I'm missing?

mezoid
  • 28,090
  • 37
  • 107
  • 148
  • Other questions/duplicates all combining to a full answer: [Why does (int)(object)10m throw “Specified cast is not valid” exception?](https://stackoverflow.com/q/3953391/11683), [Cast object containing int to float results in InvalidCastException](https://stackoverflow.com/q/24447387/11683), [Casting object to int throws InvalidCastException in C#](https://stackoverflow.com/q/39891504/11683) – GSerg Jul 19 '19 at 08:07

4 Answers4

85

You can only unbox a value type to its original type (and the nullable version of that type).

By the way, this is valid (just a shorthand for your two line version):

object i = 4;
decimal d = (decimal)(int)i; // works even w/o decimal as it's a widening conversion

For the reason behind this read this Eric Lippert's blog entry: Representation and Identity

Personally, I categorize things done by cast syntax into four different types of operation (they all have different IL instructions):

  1. Boxing (box IL instruction) and unboxing (unbox IL instruction)
  2. Casting through the inhertiance hierarchy (like dynamic_cast<Type> in C++, uses castclass IL instruction to verify)
  3. Casting between primitive types (like static_cast<Type> in C++, there are plenty of IL instructions for different types of casts between primitive types)
  4. Calling user defined conversion operators (at the IL level they are just method calls to the appropriate op_XXX method).
marbel82
  • 925
  • 1
  • 18
  • 39
Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
  • 22
    In a sense it's a shame that unboxing and casting syntactically look identical, since they are very different operations. – jerryjvl Jul 06 '09 at 01:59
  • Thanks Mehrdad. Your explanation and link to Eric's blog was quite helpful. – mezoid Jul 06 '09 at 02:29
  • Thank you! This threw me for a loop. – Darryl Sep 24 '09 at 19:18
  • But if the value is sometimes a full number but other times a true decimal. casting to int first will lose the decimal value. If you expecting a double then MAKE sure by validation that the source gives you a dobule. Otherwise you will get side effects that will make the end user scratch his hair out and then poke your eyeballs. Good answer but very bad advise- It deserves a -1 – Piotr Kula Oct 15 '12 at 12:41
  • @ppumkin: You seem to be missing the whole point. You will _not_ be able to cast `object a = (decimal)5;` with `(int)a` directly. It will throw an `InvalidCastException`. – Mehrdad Afshari Oct 15 '12 at 17:46
  • What if the value is `5.00` then `(decimal)5.00` is valid, yea? Can you guarantee that it wont be 5.00? If you can then there is no need to cast it to decimal at all. Unless I really mis understand something here? – Piotr Kula Oct 16 '12 at 08:37
  • 2
    @ppumkin The example is not about **needing** to cast to `decimal` or any other type for that matter. It is about the possibility of cast a boxed value directly to another type, demonstrating the subtle difference between the boxing/unboxing conversions and simple primitive type conversion, despite all using the same casting syntax. – Mehrdad Afshari Oct 16 '12 at 16:10
  • OK - I undertand. I had a problem where i wanted to always have double even if it was boxed int. I googled and got this answer. I just need to make sure it is boxed as double to avoid this issue I am having. Thanks for explaining +1 – Piotr Kula Oct 17 '12 at 21:46
15

There is no problem in casting an int to decimal, but when you are unboxing an object you have to use the exact type that the object contains.

To unbox the int value into a decimal value, you first unbox it as an int, then cast it to decimal:

decimal d = (decimal)(int)reader[0];

The IDataRecord interface also has methods for unboxing the value:

decimal d = (decimal)reader.GetInt32(0);
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • Thanks for your response too Guffa...it was very helpful. – mezoid Jul 06 '09 at 02:30
  • But if the value is sometimes a full number but other times a true decimal. casting to int first will lose the decimal value. If you expecting a double then MAKE sure by validation that the source gives you a dobule. Otherwise you will get side effects that will make the end user scratch his hair out and then poke your eyeballs. Good answer but very bad advise- It deserves a -1 – Piotr Kula Oct 15 '12 at 12:40
  • 1
    @ppumkin: Sorry if you misunderstood the answer. It isn't casting to `int` and then to `decimal`, it's *unboxing* an `int` and then casting to `decimal`. Unboxing the value as an `int` will never make it lose anything at all, because it's not possible to unbox it as an `int` if it's not actually an `int` to begin with. – Guffa Oct 15 '12 at 12:49
15

Here is a simple solution. It takes care of unboxing and then casting to decimal. Worked fine for me.

decimal d = Convert.ToDecimal(reader[0]);  // reader[0] is int
Sagar
  • 579
  • 5
  • 7
  • This was the answer for me - my return types seemed to be dynamically determined by the size of the number returned. This coped with converting a boxed int or a boxed decimal into an unboxed decimal. – Chris Apr 08 '18 at 17:10
3

Mehrdad Afshari said it:

You can only unbox a value type to its original type (and the nullable version of that type).

The thing to realize is that there is a difference between casting and unboxing. jerryjvl had an excellent remark

In a sense it's a shame that unboxing and casting syntactically look identical, since they are very different operations.

Casting:

int i = 3750; // Declares a normal int
decimal d = (decimal)i; // Casts an int into a decimal > OK

Boxing/Unboxing:

object i = 3750; // Boxes an int ("3750" is similar to "(int)3750")
decimal d = (decimal)i; // Unboxes the boxed int into a decimal > KO, can only unbox it into a int or int?
user276648
  • 6,018
  • 6
  • 60
  • 86