This is the story of a C# language specification change.
The specification changed because the 1.0 version of the C# spec disagreed with itself, and one of those locations was incorrect and broke a feature.
The change is in the section on “Conditional Logic Operators”. Version 1 of the spec states:
- The operation
x && y
corresponds to the operation x & y
, except that y
is evaluated only if x
is true
. - The operation
x || y
corresponds to the operation x | y
, except that y
is evaluated only if x
is false
.
The later versions (starting with version 3) state:
- The operation
x && y
correspond to the operation x & y
, except that y
is evaluated only if x
is not false. - The operation
x || y
correspond to the operation x | y
, except that y
is evaluated only if x
is not true.
Why the change?
Well, a couple sections later, the spec defines “User-Defined Conditional Logical Operators”. The C# Language does not allow you to create a user defined operator &&
or operator ||
. Instead, you must define operator |
, operator &
, operator true
and operator false
. Here is the pertinent text:
The &&
and ||
operation is evaluated by combining the user-defined operator true
or operator false
with the selected user-defined operator:
- The operation
x && y
is evaluated as T.false(x) ? x : T.&(x,y)
, …... In other words, x
is first evaluated and operator false
is invoked on the result to determine if x
is definitely false
. Then, if x
is definitely false
, the result of the operation is the value previously computed for x
. Otherwise, y
is evaluated, and the selected operator &
is invoked on the value previously computed for x
and the value computed for y
to produce the result of the operation. - The operation
x || y
is evaluated as T.true(x) ? x : T.|(x,y)
, …... In other words, x
is first evaluated and operator true
is invoked on the result to determine if x
is definitely true
. Then, if x
is definitely true
, the result of the operation is the value previously computed for x
. Otherwise, y
is evaluated, and the selected operator |
is invoked on the value previously computed for x
and the value computed for y
to produce the result of the operation.
The key points here are that for operator &&
, x
is checked to ensure that it is not false
, and for operator ||
, x
is checked to ensure that it is not true
.
Why the Spec Had to Change
The version 1.0 of the spec had serious limitations. User Defined Types that defined operator &
or operator |
would work with if and only if operator true
and operator false
were defined such that exactly one of them was true
at all times.
Nothing in the language mandates that explicitly.
As a thought exercise, suppose you have a type that may be neither true
nor false
in some states. Maybe there are ranges of “true
” and “false
” and a range in between of “neither true nor false”.
If you want a concrete example, consider a type that has a single byte field. Its operator true
returns true
when all bits are 1
. Its operator false
returns true
when all bits are 0
. In all cases, to evaluate ‘x && y
’, or ‘x || y
’ requires both the left and right side of the operator. No short circuiting is possible.
That’s why the spec changed at version 3.
A Little More Explanation in the Spec
We felt the spec needed a little more explanation around this change. In ECMA version 5, we’re adding this note:
Note: The reason that short circuiting uses the 'not true' and 'not false' conditions is to enable user defined conditional operators to define when short circuiting applies. User defined types could be in a state where operator true
returns false
and operator false
returns false
. In those cases, neither &&
or ||
would short circuit.
The C# spec (thankfully) has relatively few locations where the spec disagrees with itself. But in a large document, it’s easy for them to creep in. Several people review and make corrections whenever we find them.
Bill Wagner is one of the world's foremost C# developers and a member of the ECMA C# Standards Committee. He is President of the Humanitarian Toolbox, has been awarded Microsoft Regional Director and .NET MVP for 10+years, and was recently appointed to the .NET Foundation Advisory Council. Wagner currently works with companies ranging from start-ups to enterprises improving the software development process and growing their software development teams.