Why can I omit the subsequent null-conditional operators in an invocation chain?
Asked Answered
E

2

8

Consider the following code:

IEnumerable<int> xx = null;
var tt = xx?.Where(x => x > 2).Select(x => x.ToString());

It assigns null to tt. The question is: why does it work properly?

I thought I must use ?. before Select as ?.Where(...) returns null. Besides, if I split the second line into two separate lines:

IEnumerable<int> xx = null;
var yy = xx?.Where(x => x > 2);
var zz = yy.Select(x => x.ToString());

There will be the ArgumentNullException on the third line as yy == null.

What's the magic? :)
If this is because of short-circuiting, I've never thought that it can act like this.

Expendable answered 16/2, 2018 at 17:1 Comment(8)
WHERE doesn't return null - it will return empty IEnumerable<T> - so it can't be null. You might have been thinking about FirstOrDefault()Mercuric
@VidmantasBlazevicius Where isn't even invoked.Okeechobee
@VidmantasBlazevicius Why doesn't? yy in the second block is null.Expendable
Where doesn't get called because xx is null.Boyle
You're second bit of code would be equivalent to var tt = (xx?.Where(x => x > 2)).Select(x => x.ToString())Ashtonashtonunderlyne
@Ashtonashtonunderlyne Yes, I know, and there's no wonder there, as for the first example.Expendable
Even this works: xx?.Where(x => x > 2).Select(x => x.ToString()).First().Length.GetHashCode();Expendable
The accepted answer is correct, but if this weren't the case (hypothetically) they could still have implemented the extension methods to accept and return null instead of throwing an exception. In that case, your second example would also have worked.Xerosis
K
12

Yes, this is due to short-circuiting. From the MSDN reference:

...[T]he null-condition operators are short-circuiting. If one operation in a chain of conditional member access and index operation returns null, then the rest of the chain’s execution stops.

The reason your second example throws is because you have separate unchained statements. Short-circuiting cannot be applied across multiple statements.

Kendrakendrah answered 16/2, 2018 at 17:6 Comment(6)
Technically, that cannot be an explanation. I have the chain of one conditional member acces and one -- unconditional. No chain of conditional member access, as in the docs.Expendable
MSDN isn't as explicit as it could be, but that is the explanation. Any other chained operations with the same precedence are ignored, whether they use the conditional or not. In the next sentence, MSDN clarifies that only operations with a lower precedence will continue to execute.Kendrakendrah
Okay, I agree with this, especially with addition of your clarification. Thank you.Expendable
So we can omit conditional (use just subsequent unconditional operators) unless we get the may-be-null output from non-null input, right?Expendable
Yes (provided I'm understanding you correctly). Once you've gotten past the thing-that-might-be-null, all of the subsequent operators can be non-conditional until/unless you reach another thing-that-might-be-null.Kendrakendrah
Yes, I meant just that. Thanks again.Expendable
P
4

The null-conditional operator or also known as the null propagation operator is short-circuiting i.e if one operation in the chain:

var tt = xx?.Where(x => x > 2).Select(x => x.ToString());

returns null, then the rest of the chain’s execution stops.

So in the above example Where is never invoked as xx is null.

As for the second example, you're getting an ArgumentNullException because that's the behaviour of extension methods. in this specific case, the Select throws a ArgumentNullException when the source or the provided selector is null.

Presswork answered 16/2, 2018 at 17:6 Comment(2)
"then the rest of the chain’s execution stops." Whatever further is in the chain, conditionals or not.Expendable
BJ Myers has already provided a link to the MSDN page that explains this in detail. So my answer is pretty much stating the documentation.Presswork

© 2022 - 2024 — McMap. All rights reserved.