The C# standard has this note:
Note: Although true and false are not used explicitly in expressions (and therefore are not included in the precedence table in §12.4.2), they are considered operators because they are invoked in several expression contexts: Boolean expressions (§12.24) and expressions involving the conditional (§12.18) and conditional logical operators (§12.14). end note
Following those links, you'll find that the false operator is used in conditional logical operators:
The operation x && y is evaluated as T.false(x) ? x : T.&(x, y), where T.false(x) is an invocation of the operator false declared in T, and T.&(x, y) is an invocation of the selected operator &. 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.
Here's some test code to show the various times that operators are used:
LoggingBoolean t = new LoggingBoolean(true);
LoggingBoolean f = new LoggingBoolean(false);
Console.WriteLine("Boolean expression");
if (t)
{
Console.WriteLine("In true body");
}
if (f)
{
Console.WriteLine("In false body");
}
Console.WriteLine("Conditional operator");
Console.WriteLine(t ? "true branch" : "false branch");
Console.WriteLine(f ? "true branch" : "false branch");
Console.WriteLine("Conditional && logic");
Console.WriteLine(t && t);
Console.WriteLine(t && f);
Console.WriteLine(f && t);
Console.WriteLine(f && f);
Console.WriteLine("Conditional || logic");
Console.WriteLine(t || t);
Console.WriteLine(t || f);
Console.WriteLine(f || t);
Console.WriteLine(f || f);
internal struct LoggingBoolean
{
public bool Value { get; }
public LoggingBoolean(bool value) => Value = value;
public static bool operator false(LoggingBoolean x)
{
Console.WriteLine("false operator called");
return !x.Value;
}
public static bool operator true(LoggingBoolean x)
{
Console.WriteLine("true operator called");
return x.Value;
}
public static LoggingBoolean operator &(LoggingBoolean x, LoggingBoolean y) =>
new LoggingBoolean(x.Value && y.Value);
public static LoggingBoolean operator |(LoggingBoolean x, LoggingBoolean y) =>
new LoggingBoolean(x.Value || y.Value);
public override string ToString() => $"LoggingBoolean({Value})";
}
Output:
Boolean expression
true operator called
In true body
true operator called
Conditional operator
true operator called
true branch
true operator called
false branch
Conditional && logic
false operator called
LoggingBoolean(True)
false operator called
LoggingBoolean(False)
false operator called
LoggingBoolean(False)
false operator called
LoggingBoolean(False)
Conditional || logic
true operator called
LoggingBoolean(True)
true operator called
LoggingBoolean(True)
true operator called
LoggingBoolean(True)
true operator called
LoggingBoolean(False)