I have this code.

byte dup = 0;Encoding.ASCII.GetString(new byte[] { (0x80 | dup) });

When I try to compile I get:

Cannot implicitly convert type 'int'to 'byte'. An explicit conversionexists (are you missing a cast?)

Why does this happen? Shouldn't | two bytes give a byte? Both of the following work, assuring that each item is a byte.

Encoding.ASCII.GetString(new byte[] { (dup) });Encoding.ASCII.GetString(new byte[] { (0x80) });
3

Best Answer


It's that way by design in C#, and, in fact, dates back all the way to C/C++ - the latter also promotes operands to int, you just usually don't notice because int -> char conversion there is implicit, while it's not in C#. This doesn't just apply to | either, but to all arithmetic and bitwise operands - e.g. adding two bytes will give you an int as well. I'll quote the relevant part of the spec here:

Binary numeric promotion occurs forthe operands of the predefined +, –,*, /, %, &, |, ^, ==, !=, >, <, >=, and <= binary operators. Binarynumeric promotion implicitly convertsboth operands to a common type which,in case of the non-relationaloperators, also becomes the resulttype of the operation. Binary numericpromotion consists of applying thefollowing rules, in the order theyappear here:

  • If either operand is oftype decimal, the other operand isconverted to type decimal, or acompile-time error occurs if the otheroperand is of type float or double.

  • Otherwise, if either operand is oftype double, the other operand isconverted to type double.

  • Otherwise,if either operand is of type float,the other operand is converted to typefloat.

  • Otherwise, if either operandis of type ulong, the other operand isconverted to type ulong, or acompile-time error occurs if the otheroperand is of type sbyte, short, int,or long.

  • Otherwise, if eitheroperand is of type long, the otheroperand is converted to type long.

  • Otherwise, if either operand is oftype uint and the other operand is oftype sbyte, short, or int, bothoperands are converted to type long.

  • Otherwise, if either operand is oftype uint, the other operand isconverted to type uint.

  • Otherwise,both operands are converted to typeint.

I don't know the exact rationale for this, but I can think about one. For arithmetic operators especially, it might be a bit surprising for people to get (byte)200 + (byte)100 suddenly equal to 44, even if it makes some sense when one carefully considers the types involved. On the other hand, int is generally considered a type that's "good enough" for arithmetic on most typical numbers, so by promoting both arguments to int, you get a kind of "just works" behavior for most common cases.

As to why this logic was also applied to bitwise operators - I imagine this is so mostly for consistency. It results in a single simple rule that is common for all non-boolean binary types.

But this is all mostly guessing. Eric Lippert would probably be the one to ask about the real motives behind this decision for C# at least (though it would be a bit boring if the answer is simply "it's how it's done in C/C++ and Java, and it's a good enough rule as it is, so we saw no reason to change it").

The literal 0x80 has the type "int", so you are not oring bytes.

That you can pass it to the byte[] only works because 0x80 (as a literal) it is within the range of byte.

Edit: Even if 0x80 is cast to a byte, the code would still not compile, since oring bytes will still give int. To have it compile, the result of the or must be cast: (byte)(0x80|dup)

byte dup = 0;Encoding.ASCII.GetString(new byte[] { (byte)(0x80 | dup) });

The result of a bitwise Or (|) on two bytes is always an int.