Search code examples
c#booleanboolean-logicboolean-expressionboolean-operations

Bool in C# works something unclear for me


Here goes an example of C# code. I did not get why in first case we will have 1 instead of 0.

Why does it return 1 instead?

static void Main(string[] args)
{
    int firstNumber = 0;

    bool simpleLogicResult = true & false & (firstNumber++ > 0); 

    Console.WriteLine($"firstNumber = {firstNumber}");

    int secondNumber = 0;

    bool shortCircuitResult = true && false && (secondNumber++ > 0); 

    Console.WriteLine($"secondNumber = {secondNumber}");

    // Delay
    Console.ReadKey();
}

I expect 0 instead of 1 :)


Solution

  • The base reason is that the & bitwise operator does not short-circuit while the && logical operator does. As you've indicated in your variable names, you're at least aware of this fact.

    (It's not so clear whether you understand how the post-increment ++ operator works. It fetches the value of the operand (firstNumber, value: 0), increments the operand, then returns the pre-increment value. So the comparison > 0 results in false in both cases. Which may or may not be germane to the question.)

    The compiler can evaluate all of the terms during compilation because all of the values are either constant or well known. It is free to peform expression reduction, and that's exactly what actually happens.

    Let's look at your first two lines:

    int firstNumber = 0;
    bool simpleLogicResult = true & false & (firstNumber++ > 0);
    

    Two things are happening here.

    The first is that all three terms in the second line are compiled, producing a set of known or constant values and an increment operation. All three must be full evaluated, including all side effects, because the & bitwise operator does not short-circuit the operands.

    Secondly, the compiler performs expression simplification, pre-calculating the answer as far as possible. Since all terms in the calculation are constant the expression can be pre-calculated at compile time, and all side effects (the increment operation in this case) are preserved.

    What it produces, when you reverse the compiled IL back to C#, is this:

    int firstNumber = 0;
    firstNumber++;
    bool simpleLogicResult = false;
    

    Now, your second calculation uses the && operator:

    int secondNumber = 0;
    bool shortCircuitResult = true && false && (secondNumber++ > 0);
    

    The first operation true && false results in a constant false value, so the third term will not be evaluated due to short-circuiting. So when the compiler is working on this it doesn't have to queue the increment operation as it did for the arithmetic operation above.

    So what we end up with after compilation and de-compilation is this:

    int secondNumber = 0;
    bool shortCircuitResult = false;
    

    And for bonus points, the ++ pre-increment operator would produce the exact same results in this specific case, since you've included a false term in the expression for both the bitwise & and logical && expressions. If we change your expression though the results are different:

    int thirdNumber = 0;
    bool result = true & (++thirdNumber > 0);
    

    In this case the resulting lowered code (decompiling from the generated IL) is:

    int thirdNumber = 0;
    bool result = ++thirdNumber > 0;
    

    Sadly the pre-increment appears to be a little harder for the compiler to optimize, but the result is the same: thirdNumber is 1 and result is true.