views:

574

answers:

3

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 conversion exists (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) });
+4  A: 
byte dup = 0;
Encoding.ASCII.GetString(new byte[] { (byte)(0x80 | dup) });

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

blesh
As people pointed out 0x80 is an int literal. regardless, the issue at hand is int | byte returns an int, which can't be stuck in a byte[].
blesh
+5  A: 

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)

Martin v. Löwis
true, but still doesn't show how to fix it. simply casting 0x80 to byte still results in an error message because byte|byte => int.
Lucas
(byte)(0x80|dup)
pb
Sorry, voted down. Doesn't explain the real problem at play here: byte | byte = int32.
blesh
@blesh: no, that's not the real problem. 0x80 is of type int, not of type byte - contrary to what the OP claims (and apparently you also claim). So it's int|byte. Of course, if you were to fix it as (byte)0x80 | dup, it still wouldn't work because of the reason Lucas and you give.
Martin v. Löwis
The issue is a number in C# (a literal) is an integer. byte b = 42; is an integer not a byt
Chris S
The asker was premature picking this as the solution because it didn't actually address the problem. The problem was he was getting an exception because (0x80 | dup) was returning an int... your solution to assure that you're casting both or'ed integrals by casting 0x80 as a byte will still throw the Exception. byte[] { ((byte)0x80 | dup) }; //throws the same exception.This question needs moderated so someone doesn't come here looking for an answer and leave with the wrong one.
blesh
@blesh: you are mistaken claiming that Exceptions have to do anything with it. Instead, it's a compile time error, not a runtime error. In any case, I have added the discussion of byte|byte into the answer.
Martin v. Löwis
+14  A: 

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 for the operands of the predefined +, –, *, /, %, &, |, ^, ==, !=, >, <, >=, and <= binary operators. Binary numeric promotion implicitly converts both operands to a common type which, in case of the non-relational operators, also becomes the result type of the operation. Binary numeric promotion consists of applying the following rules, in the order they appear here:

  • If either operand is of type decimal, the other operand is converted to type decimal, or a compile-time error occurs if the other operand is of type float or double.

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

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

  • Otherwise, if either operand is of type ulong, the other operand is converted to type ulong, or a compile-time error occurs if the other operand is of type sbyte, short, int, or long.

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

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

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

  • Otherwise, both operands are converted to type int.

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").

Pavel Minaev
This is really interesting. This post deservses a lot of votes:)
Stilgar
sweet answer! good detail! Could've used a code snippet to demonstrate what would fix his problem, but all in all very interesting.
blesh
For completeness it would be worth mentioning that the `|=` etc operators do a cast implicitly.
romkyns