I know that ||
and &&
are defined as short-circuit operators in C#, and such behaviour is guaranteed by the language specification, but do |=
and &=
short-circuit too?
For example:
private bool IsEven(int n) { return n % 2 == 0; } private void Main() { var numbers = new int[] { 2, 4, 6, 8, 9, 10, 14, 16, 17, 18, 20 }; bool allEven = true; bool anyOdd = false; for (int i = 0; i < numbers.Length; i++) { allEven &= IsEven(numbers[i]); anyOdd |= !IsEven(numbers[i]); } }
When the 9 entry is hit, allEven
becomes false, meaning that all subsequent entries are irrelevant - the value of allEven
is guaranteed to be false for all future calls to that expression. The same goes for anyOdd
, which is set to true when it sees 9, and will remain true for all subsequent calls to that expression.
So, do &=
and |=
shortcut, or is IsEven
guaranteed to be called on every iteration? Is there any defined behaviour in the language specification for this case? Are there any corner-cases where such short circuiting would be problematic?
Short-circuit evaluation The logical AND expression is a short-circuit operator. As each operand is converted to a boolean, if the result of one conversion is found to be false , the AND operator stops and returns the original value of that falsy operand; it does not evaluate any of the remaining operands.
Short-circuit evaluation, minimal evaluation, or McCarthy evaluation (after John McCarthy) is the semantics of some Boolean operators in some programming languages in which the second argument is executed or evaluated only if the first argument does not suffice to determine the value of the expression: when the first ...
|= just assigns the bitwise OR of a variable with another to the one on the LHS.
The || OR operator is also a short-circuit operator. Since OR evaluates to true when one or both of its operands are true , short-circuit evaluation stops with the first true . The OR operator comes in a non-short-circuit version as well: | (this is a single vertical bar.)
The C# specification guarantees that both sides are evaluated exactly once from left-to-right and that no short-circuiting occurs.
5.3.3.21 General rules for expressions with embedded expressions
The following rules apply to these kinds of expressions: parenthesized expressions (§7.6.3), element access expressions (§7.6.6), base access expressions with indexing (§7.6.8), increment and decrement expressions (§7.6.9, §7.7.5), cast expressions (§7.7.6), unary +, -, ~, * expressions, binary +, -, *, /, %, <<, >>, <, <=, >, >=, ==, !=, is, as, &, |, ^ expressions (§7.8, §7.9, §7.10, §7.11), compound assignment expressions (§7.17.2), checked and unchecked expressions (§7.6.12), plus array and delegate creation expressions (§7.6.10).
Each of these expressions has one or more sub-expressions that are unconditionally evaluated in a fixed order.
The C# specification for compound operators says:
7.17.2 Compound assignment
...
An operation of the form
x op= y
is processed by applying binary operator overload resolution (§7.3.4) as if the operation was writtenx op y
. Then,
If the return type of the selected operator is implicitly convertible to the type of
x
, the operation is evaluated asx = x op y
, except thatx
is evaluated only once.Otherwise, if the selected operator is a predefined operator, if the return type of the selected operator is explicitly convertible to the type of
x
, and ify
is implicitly convertible to the type ofx
or the operator is a shift operator, then the operation is evaluated asx = (T)(x op y)
, where T is the type ofx
, except thatx
is evaluated only once....
In your case op
is &
or |
. The short circuiting behavior mirrors that of &
/|
and not &&
/||
.
Note that this only refers to behavior visible in a single threaded scenario. So if the right hand side has no side-effects that are observable in such a scenario, the compiler or JITter is still free to omit the evaluation.
In your example the compiler is free to terminate the loop once it knows the result, since there are no such side-effects. But it's not required to do so.
In particular timing does not count as such a side-effect, and you thus can't rely on your code having constant runtime. This can be problematic in a security context, since it can introduce a timing side-channel.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With