0

Some of my knowledge about the topic is as following. If consecutive operators in an expression have the same precedence, a rule called associativity is used to decide the order in which those operators are evaluated. Further, left-associative operators of the same precedence are evaluated in order from left to right.

What I don't understand is why the following code doesn't throw exception.

if (

    object == null || 
    object.Flag && 
    object.Status == object2.Status

)

What if object is null? In this case, doesn't the call of object.Status throws exception because of high precedence of == over &&, likewise && over ||? I mean,

if (

    (object == null) || 
    (object.Flag && 
    (object.Status == object2.Status))

)

The call order,

  1. object.Status == object2.Status
  2. object.Flag
  3. object == null

What point do I overlook?

Hax
  • 133
  • 9

1 Answers1

1

First, your statement:

The call order,

  1. object.Status == object2.Status
  2. object.Flag
  3. object == null

Is incorrect. From the spec:

Unrelated to operator precedence and associativity, operands in an expression are evaluated from left to right.

So the evaluation order is (or would be if || wasn't short-circuiting, see below):

  1. object
  2. null
  3. object == null
  4. object.Flag
  5. object.Status
  6. object2.Status
  7. object.Status == object2.Status
  8. object.Flag && (object.Status == object2.Status)
  9. object == null || (object.Flag && (object.Status == object2.Status))

That gets us half the way there.

The final piece is that the || and && operators are short-circuiting. In the expression a || b, if a is true, then b won't be evaluated.

From the spec:

Typically, all operator operands are evaluated. However, some operators evaluate operands conditionally. That is, the value of the leftmost operand of such an operator defines if (or which) other operands should be evaluated. These operators are the conditional logical AND (&&) and OR (||) operators, the null-coalescing operators ?? and ??=, the null-conditional operators ?. and ?[], and the conditional operator ?:

So if object == null is true, then the other side of the || (that is, the bits that access properties on object) are never evaluated.

Community
  • 1
  • 1
canton7
  • 37,633
  • 3
  • 64
  • 77
  • Beat me to it ;) Just to add if you used non-short circuiting `|` and `&` the result would be different if object where null. @Hax You would see a null reference exception thrown in your case. – devtoka May 28 '20 at 10:28
  • One point is overlooked. In my second if statement, the inner most == operator is evaluated first. Isn't it? If not, **the short cir. eva. rule ruins the operator precedence?** – Hax May 28 '20 at 10:32
  • @Hax no. I added to bit to the beginning of my answer, which talks about evaluation. Evaluation happens independently of precedence and associativity. The `&&` operator affects the evaluation of the things on its right-hand side, which includes the evaluation of `object.Flag`, `object.Status` and `object2.Status`. – canton7 May 28 '20 at 10:35
  • Both your examples will have the same result and will be executed in the same order – devtoka May 28 '20 at 10:35
  • First evaluation(s) happens, then precedence is applied? Right? – Hax May 28 '20 at 10:45
  • @Hax See the examples [here](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/#operand-evaluation). Operands in expressions are evaluated left-to-right. Operands can themselves be expressions. See in the examples how `a / b + c * d` is evaluated as `a, b, /, c, d, *, +`. `a`, `b`, `c`, and `d` are evaluated strictly left-to-right, but interleaved with the `/`, `*` and `+` operators as dictated by precedence and associativity. – canton7 May 28 '20 at 10:47
  • Another way of looking at it is to say that `a / b + c * d` is `(a / b) + (c * d)` (by precedence). We start with the `+`, and evaluate its operands `(a / b)` and `(c * d)` left-to-right. That means we start with `(a / b)`. Again we evaluate the operands of `/` left-to-right, so evaluate `a` then `b`. Then we evaluate `a / b`. Then we come to evaluate the right-hand operand of the `+`, which is `(c * d)`, which we against evaluate left-to-right, doing `c` then `d` then `c * d`. – canton7 May 28 '20 at 10:53