12

Is there anyone able to explain me this strange behavior?

    int i = 0x1234;
    byte b1 = (byte)i;
    byte b2 = (byte)0x1234;         //error: const value '4660' can't convert to byte (use unchecked)
    byte b3 = unchecked((byte)0x1234);
    byte b4 = checked((byte)i);     //throws
    byte b5 = (byte)(int)0x1234;    //error: same as above

NOTE: It's an empty Console application, with NO arithmetic checking enabled (as default is). Thank you everybody in advance.

EDIT: I supposed to be clear enough, but not for all.

I do know that a word can't fit into a byte. But, by default, a C# program allows certain "dangerous" operations, primarily for performance reason.

Similarly, I may sum two large integers together and having no overflow at all.

My wonder was about the compile-time error above: the b1 cast/assignment is compiled, the b2 can't compile. Apparently there's no difference, because both are Int32 having the same value.

Hope it's clear now.

Mario Vernari
  • 6,649
  • 1
  • 32
  • 44
  • 3
    I don't speak c# and I don't know what "checked" and "unchecked" mean, but I know that you can't fit a four-digit hex number in a byte. – Colin Fine Oct 19 '11 at 14:55
  • 1
    Which behaviour is 'strange' to you? You have given us **steps to reproduce** and **actual result**, but not **expected result**. – AakashM Oct 19 '11 at 14:58

3 Answers3

15

You're tripping over a part of section 7.19 of the C# 4 spec:

Unless a constant expression is explicitly placed in an unchecked context, overflows that occur in integral-type arithmetic operations and conversions during the compile-time evaluation of the expression always cause compile-time errors.

Basically, the point is that even if you're happy to allow operations to overflow at execution time, if you're trying to use a constant expression which can't be converted to the target type at compile time, you have to tell the compiler that you really know what you're doing.

For example, in this case you're losing information - it'll be equivalent to

byte b3 = 0x34;

so you'd normally be better off just specifying that, to give you clearer code which doesn't mislead the reader. It's relatively rare that you want to overflow in a constant - most of the time you should just specify the valid value instead.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 4
    You sound like a lawyer quoting the _"section 7.19 of the C# 4 spec"_. :D – Otiel Oct 19 '11 at 15:03
  • For me that's all right, but why the compiler does not take in account the "Arithmetic overflow" disabling as well? Thank you anyway: it was just a curiosity I bumped to. – Mario Vernari Oct 19 '11 at 15:03
  • @MarioVernari: Because that's designed to change the execution-time behaviour - most C# code is probably built with that default, but most devs should still care if their *constants* overflow. – Jon Skeet Oct 19 '11 at 15:05
  • 2
    @Leito: I don't mind being a language lawyer - the spec is (to some extent, anyway) the real authority on the language, so giving a spec reference is good practice when trying to justify compiler behaviour IMO. – Jon Skeet Oct 19 '11 at 15:05
  • 1
    And I agree with you. I was just making an off-topic comment - SO users forgive me - because it's not common to have those kind of answers, quoting sections, chapters and all. It's all credit to you. :) – Otiel Oct 19 '11 at 15:08
  • @Leito: It's just a pity that there isn't a good multi-page HTML version of the full C# 4 spec online, otherwise I could include a link, as I usually do with the JLS. I believe MSDN has a version of the C# spec, but I don't think it's laid out in quite the same way... – Jon Skeet Oct 19 '11 at 15:09
  • @phoog: Thanks, fixed - and the typo of "expressions" :) – Jon Skeet Oct 19 '11 at 16:25
1

This is not a strange behaviour, the valid range for a variable of the byte data type is 0-255 but when you convert HEX 0x1234 value into decimal system you got 4660. So unchecked used to control the overflow-checking integral-type arithmetic operations and conversions.

You can find that unchecked often used in GetHashCode() implementation which does numeric operations to calculate the final hash code.

To summarize you should use unchecked when the final result value of integer-type operations is not matter but overflow could happen.

sll
  • 61,540
  • 22
  • 104
  • 156
0

You shouldn't surround this with unchecked. Unchecked allows assignment of dangerous value types to a type, which may cause overflows.

byte b1 = (byte)i; will cause an overflow or cast exception at runtime.

byte b2 = (byte)0x1234; is invalid because you can't store values larger than 0xFF in a byte.

byte b3 = unchecked((byte)0x1234); will place either 0x34 or 0x12 (depending on the CLR implementation) into b3, and the other byte will overflow.

byte b4 = checked((byte)i); is the same as byte b1 = (byte)i;

byte b5 = (byte)(int)0x1234; will cast 0x1234 to an int, and then try to cast it to byte. Again, you can't convert 0x1234 to a byte because it's too large.

Polynomial
  • 27,674
  • 12
  • 80
  • 107
  • 1
    Well, you're partially right, but not at all. It's true that avoiding checks is a dangerous practice, but you should also use them with a bit of brain, IMHO. It's NOT true, that your first assignment cause an exception. It'll throw only if you enable the "arithmetic checking" (default is off). The third assignment I guess is wrong: it should return always 0x12. Fourth: same considerations of the first. Fifth: same as second. Cheers – Mario Vernari Oct 19 '11 at 15:18
  • 3
    The third will always return 0x34, not 0x12. And the first will only cause an exception if checking is enabled AND if the value is outside the range 0-255. – phoog Oct 19 '11 at 16:23