views:

341

answers:

5

I found an odd situation in the C# compiler. Why the cast below is required?

using System;

class Program
{
    private const byte BIT_ZERO_SET = 1;
    private const byte BIT_ONE_SET = 2;
    private const byte BIT_TWO_SET = 4;

    static void Main(string[] args)
    {
        byte b = BIT_ZERO_SET | BIT_ONE_SET;
        Console.WriteLine(b);

        //Does not compile, says needs to cast to int.
        //b = b | BIT_TWO_SET;

        //Compiles...ugly
        b = (byte)(b | BIT_TWO_SET);
        Console.WriteLine(b);

        Console.WriteLine("Press enter.");
        Console.ReadLine();    
    }
}

Thanks.

+6  A: 

It is definitely strange, but what is happening, is the result of b|BIT_TWO_SET is an integer.

this works: b = (byte)(b | BIT_TWO_SET); because the result is an int in that case.

Also, you could replace that line with : b |= BIT_TWO_SET; which works.

Erich
I did not know there was a |=. Thanks!
Nate
Christian Hayter
+4  A: 

Is suspect that the line:

byte b = BIT_ZERO_SET | BIT_ONE_SET;

is actually processed by the C# compiler into an assignment of a constant value to b, rather than a bitwise operation - it can do this because the right side of the expression is fully defined at compile time.

The line:

//b = b | BIT_TWO_SET;

doesn't compiled because the bit-wise OR operator promotes its elements and evaluates to an int, not a byte. Since it involves a runtime value (b) it cannot be compiled into a constant assignment like the line before, and requires casting.

LBushkin
That makes sense...
Nate
+1, Yes, the compiler will compute constant expressions at compile time. Which explains why the 1st assignment does __not__ give an error.
Henk Holterman
@Godeke: How are you saying anything different than he is? He's saying that the resulting value of a bitwise `OR` on a `byte` is an int.
Adam Robinson
The whole constant expression thing is a red herring, that's all. | will *never* return a byte without casting, it only returns int, uint, long or ulong.
Godeke
EMCA-334 14.10 Logical Operators
Godeke
Unfortunately the same trick can't be used for _unsetting_ bits, because that requires a `~BIT_TWO_SET`. This doesn't even work for constants, whether signed or unsigned (because of the specifics of the implicit cast rules)
romkyns
+1  A: 
        b = (byte)(b | BIT_TWO_SET);

is all you need to cast to make it compile, at least in Visual Studio 2008 against 2.0. It appears that | promotes the byte to int and you have to demote it again by hand.

Yep... a quick run past the standard shows that | returns int (or uint or long or ulong).

Godeke
Yeah, I changed my post after I realized that. Thanks.
Nate
A: 

Instead of using

 b = b | BIT_TWO_SET;

use this:

 b |= BIT_TWO_SET;

funny huh.

csharptest.net
+5  A: 

The various answers here are generally correct, but have a bunch of different facts spread out all over the place. The relevant points are:

1) The result of byte | byte is an int, because there is no | operator defined on bytes.

2) Computations involving only integral compile-time constants are treated as "checked" arithmetic; that is, the compiler verifies that the constant result does not overflow, and so on. A pleasant consequence of this fact is that an assignment of a compile-time constant integer to a variable or constant of a smaller type automatically verifies that the constant integer fits into the smaller type. If it does, then the assignment is allowed without an explicit cast. If it does not, then a compile-time error occurs. (Use the "unchecked" expression if you want to override this behaviour.)

3) Assignments from non-constant expressions of type int to byte require casts, because the compiler has no way of knowing that the result definitely fits in the byte.

4) The compound assignment operators automatically insert a cast to the operand type as part of their operation, precisely so that expressions like b |= whatever work as you'd expect.

Those four facts should explain all the behaviour that you guys have pointed out.

Eric Lippert