James Law
James Law

Reputation: 6713

C# Null Coalescing Operator Behaves Differently in Conjunction with OR Operator Between Null and Empty Collection

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

Answers (3)

Pavel Anikhouski
Pavel Anikhouski

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

Alsty
Alsty

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

yaakov
yaakov

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

Related Questions