Reputation: 6713
I've observed some odd behaviour with the null coalescing operator in conjunction with the OR operator and wondered if anybody could shed some light this.
var nullCollection = null as IEnumerable<string>;
var emptyCollection = Enumerable.Empty<string>();
Console.WriteLine(nullCollection?.Any() ?? false || true);
Console.WriteLine(emptyCollection?.Any() ?? false || true);
Yields the following output:
True
False
Simply evaluating nullCollection?.Any() ?? false
or emptyCollection?.Any() ?? false
both yield False
, which would be the expected behaviour, but combining the condition with the ||
operator seems to cause the second condition to not be evaluated, but only in cases where the collection is empty.
Adding some additional parenthesis to the null coalescing condition seems to solve the problem:
Console.WriteLine((nullCollection?.Any() ?? false) || true);
Console.WriteLine((emptyCollection?.Any() ?? false) || true);
Yielding:
True
True
Why is this? What's going on here? What am I missing?
Upvotes: 3
Views: 656
Reputation: 23268
According to operators precedence, conditional OR operator ||
has higher priority than null-coalescing operator ??
.
According to null-coalescing operator specs:
The
??
operator doesn't evaluate its right-hand operand if the left-hand operand evaluates to non-null.
So, in your code adding a parenthesis is needed to make the correct order of execution and ??
operator evaluation.
Without parenthesis this nullCollection?.Any()
returns null
, therefore ??
operator returns result of false || true
(because of priority, mentioned above), which is always true
.
emptyCollection?.Any()
result is not null
and returns false
, therefore right side false || true
isn't evaluated, compiler is smart enough to understand it.
Adding a parenthesis force the compiler to perform the expected execution of operators:
(nullCollection?.Any() ?? false)
returns false
because left-side
operand is null
and right side operand is executed.(emptyCollection?.Any() ?? false)
also returns false
, left-side
operand isn't null
and right side isn't executed.Logical OR for false || true
returns true
in both cases and you'll see a desired behavior
Upvotes: 9
Reputation: 847
For the null collection:
nullCollection?.Any()
is null
, so you're evaluating null ?? false || true
.
The operator precedence rules means that || is evaluated first:
null ?? (false || true)
, which simplifies to null ?? true
, which then resolves to true
.
For the empty collection:
Since emptyCollection?.Any()
returns false
, it is not null and the coalesce is not performed.
Conceptually,
emptyCollection?.Any() ?? (false || true)
, is equivalent to false ?? (false || true)
or false ?? true
, so it will resolve to false
.
(of course this does not actually compile as bool
is not a nullable type)
Upvotes: 1
Reputation: 5850
The Conditional OR operator has a higher precedence than the Null-coalescing operator, as explained in the docs.
This means that in both your lines of code, false || true
is computed to be always true.
Therefore, the compiler effectively does this:
Console.WriteLine(nullCollection?.Any() ?? true);
Console.WriteLine(emptyCollection?.Any() ?? true);
In the first line the result is null, so we use the null-coalesce and print True
instead.
In the second line, the result is non-null - it is False
as the collection is empty - so False
is printed.
Upvotes: 2