24

If I have two bytes a and b, how come:

byte c = a & b;

produces a compiler error about casting byte to int? It does this even if I put an explicit cast in front of a and b.

Also, I know about this question, but I don't really know how it applies here. This seems like it's a question of the return type of operator &(byte operand, byte operand2), which the compiler should be able to sort out just like any other operator.

Community
  • 1
  • 1
MiffTheFox
  • 21,302
  • 14
  • 69
  • 94
  • 1
    See http://stackoverflow.com/questions/1214629 or http://stackoverflow.com/questions/927391 or http://stackoverflow.com/questions/941584 and you may have your answer ... – Joey Jun 20 '10 at 16:35
  • possible duplicate of [byte + byte = int... why?](http://stackoverflow.com/questions/941584/byte-byte-int-why) – nawfal May 31 '13 at 15:56

4 Answers4

27

Why do C#'s bitwise operators always return int regardless of the format of their inputs?

I disagree with always. This works and the result of a & b is of type long:

long a = 0xffffffffffff;
long b = 0xffffffffffff;
long x = a & b;

The return type is not int if one or both of the arguments are long, ulong or uint.


Why do C#'s bitwise operators return int if their inputs are bytes?

The result of byte & byte is an int because there is no & operator defined on byte. (Source)

An & operator exists for int and there is also an implicit cast from byte to int so when you write byte1 & byte2 this is effectively the same as writing ((int)byte1) & ((int)byte2) and the result of this is an int.

Community
  • 1
  • 1
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
17

This behavior is a consequence of the design of IL, the intermediate language generated by all .NET compilers. While it supports the short integer types (byte, sbyte, short, ushort), it has only a very limited number of operations on them. Load, store, convert, create array, that's all. This is not an accident, those are the kind of operations you could execute efficiently on a 32-bit processor, back when IL was designed and RISC was the future.

The binary comparison and branch operations only work on int32, int64, native int, native floating point, object and managed reference. These operands are 32-bits or 64-bits on any current CPU core, ensuring the JIT compiler can generate efficient machine code.

You can read more about it in the Ecma 335, Partition I, chapter 12.1 and Partition III, chapter 1.5


I wrote a more extensive post about this over here.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Makes me wonder if this implicit cast comes at a processing cost though... the type of data I end up doing bitwise operations on are generally old image formats (stuff like bitplane combining/splitting), so that's typically tens of thousands of bytes to process. – Nyerguds Mar 05 '20 at 10:04
  • @Nyerguds casting of native types is likely done during compilation to IL, so any byte you define in C# comes out as a uint in IL. That would mean there should be no performance hit at all, except during compile time where the effect would be very minimal. – cvbattum Jun 04 '20 at 16:31
5

Binary operators are not defined for byte types (among others). In fact, all binary (numeric) operators act only on the following native types:

  • int
  • uint
  • long
  • ulong
  • float
  • double
  • decimal

If there are any other types involved, it will use one of the above.

It's all in the C# specs version 5.0 (Section 7.3.6.2):

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.
Saurav Sahu
  • 13,038
  • 6
  • 64
  • 79
Philippe Leybaert
  • 168,566
  • 31
  • 210
  • 223
  • 1
    Basically, it's because the C# designers determined the cost was too high to write a separate the rule for bitwise operators (which cannot overflow) from that for arithmetic operators (which can). It's trivial to show that having the compiler insert an implicit cast is safe, because the value is always in range of the narrower type. But apparently the costs of specifying, writing, and testing that behavior were deemed greater than the benefit. – Ben Voigt Jun 20 '10 at 16:53
  • 1
    Actually, it has nothing to do with bitwise operators or not. There are simply no operators that act on byte types. – Philippe Leybaert Jun 20 '10 at 16:57
  • @PhilippeLeybaert: Whether or not the CLR defines such operators, nothing would prevent C# from behaving as though it did, much as C# takes every class types which doesn't overload `==` and pretends it has an overload which checks two objects of that type. – supercat Aug 28 '13 at 20:27
2

It's because & is defined on integers, not on bytes, and the compiler implicitly casts your two arguments to int.

Mau
  • 14,234
  • 2
  • 31
  • 52