Search code examples
c#.netoptimizationbitwise-or

'|' vs '||' compiler optimization in C#


I was recently asked this question in an interview which I totally got wrong but got me curious about the compiler optimizations in C# and .net

Consider the following snippet:

void Main()
{
    Console.WriteLine("Results when bitwise or is used: ");
    Console.WriteLine(FuncA() | FuncB());

    Console.WriteLine("Results when or operator is used: ");
    Console.WriteLine(FuncA() || FuncB()); 
}

bool FuncA()
{
    Console.WriteLine("Function A is executing.");
    return true;
}

bool FuncB()
{
    Console.WriteLine("Function B is executing.");
    return false;
}

Running the above results gets me following result:

Results when bitwise or is used:

Function A is executing.

Function B is executing.

True

Results when or operator is used:

Function A is executing.

True

My question here is why did the complier did not optimize when the bitwise operator is used? Just as for the C# or operator the parameters are already known to the compiler then why wouldn’t it handle it the same way for the bitwise or?


Solution

  • The && and || operators use an early-exit strategy specifically to prevent the side-effects of the right-hand term.

    This (slightly modified) C example from the Wikipedia page 280Z28 linked shows an example of where this is useful:

    int denom = 0;
    if (denom && num/denom) {
        do_something();
    }
    

    In this case, early exit from evaluation of the terms means that the program will not throw a divide-by-zero error while evaluating the condition, which is A Good Thing.

    Conversely the bit-wise operators are defined by the language specification to evaluate both terms, including all side effects, before performing the operation on them.

    One way of thinking about this is to consider the bit-wise operators as eager and the logical operators as lazy with regard to their evaluations. Eager operators will evaluate all of their operands before continuing, lazy operators will consider the operands in sequence and exit early if possible.

    Incidentally, the (hypothetical) ^^ operator cannot exit without evaluating both operands, since the result cannot be determined by a specific value of one operand as is the case with && and ||.