Null-conditional await #8630
Replies: 78 comments
-
As I am understand it's port from dotnet/roslyn#7171 |
Beta Was this translation helpful? Give feedback.
-
What if I have |
Beta Was this translation helpful? Give feedback.
-
awaiting Task yields void so you can't assign it. for a value type I believe it'll be T?. |
Beta Was this translation helpful? Give feedback.
-
Confirming @alrz 's response, If If |
Beta Was this translation helpful? Give feedback.
-
I think this proposal doesn't quit hit the key scenario, for the same reason as we made the last-minute change to "short circuit ?." Let's review that short-circuit first. We initially had users put ?. all the way through, because we said every single ?. was evaluated from left to right. If x()?.y()?.z But this wasn't so nice... (1) it created "question mark pollution" everywhere, (2) it THREW AWAY legitimate null-checking information -- in the case where Let's now look at the proposed await? x()?.y();
x()?.y()?.<<AWAIT>>; The proposal as it stands requires you to write ... KEY SCENARIO. All of that discussion is theoretical. Let's get concrete. The places where I've wanted this feature (and I've wanted it a lot) have almost all been to do with await? customers.FirstOrDefault()?.loadedTask; PROPOSAL2: Let's make the same short-circuit ?. evaluation order apply to Written out like that, the proposal feels partly natural but partly irregular/unpredictable. And to avoid the unpredictability, that's why I end up preferring @bbarry's original suggestion: PROPOSAL3: Let's say that Note: I've spent the past two months immersed in the Flow language. I really like how it lets me write clean code with a minimum of annotations and it figures them all out for me. I guess that |
Beta Was this translation helpful? Give feedback.
-
@ljw1004 But isn't your proposal an incompatible change? What if someone has come to depend on the code throwing a |
Beta Was this translation helpful? Give feedback.
-
Since |
Beta Was this translation helpful? Give feedback.
-
I would find it confusing if |
Beta Was this translation helpful? Give feedback.
-
I think I gave the wrong impression. I was just pointing out an issue that would arise if we want to deliberately avoid |
Beta Was this translation helpful? Give feedback.
-
@ljw1004 I think there is a compatibility issue with your proposal. If we add the feature that I think that is a fatal flaw. |
Beta Was this translation helpful? Give feedback.
-
What if said change was tied to a CLR version upgrade (say for default interface methods). A compiler targeting the new CLR could let |
Beta Was this translation helpful? Give feedback.
-
@alrz Always checking for null is no worse than what every |
Beta Was this translation helpful? Give feedback.
-
@gafter Just brainstorming. To solve the old compiler problem, it could be useful to plan to add the capacity to opt in to new compiler behaviors in the csproj SDK which translate to csc.exe switches, in the same vein as Default template, allows
|
Beta Was this translation helpful? Give feedback.
-
@bbarry @jnm2 I'm not sure this is a problem that can be solved with technical solutions. I think it's an undesirable situation when you find some code e.g. on Stack Overflow, you copy it to your project and it's broken in a subtle way, because you're using a different version of the compiler (compiler error is fine, exception isn't). And neither of your solutions changes that. |
Beta Was this translation helpful? Give feedback.
-
@gafter agreed it's a fatal flaw. That's a shame. |
Beta Was this translation helpful? Give feedback.
-
Good point, I hadn't thought of returning non-nullable value types. My first response would have been to go with |
Beta Was this translation helpful? Give feedback.
-
I disagree with this, for the same reason I think that Yes, it will almost always throw when it encounters a bug in the code. But that's a virtue. It tells me when I've done something very wrong. Nulls should not be papered over. They should either be explicitly expected, or they should fail catastrophically. Silently ignoring them is just a way to lead to even worse bugs happening |
Beta Was this translation helpful? Give feedback.
-
Ok but what's the logic for allowing
This is true but it's also worth pointing out that Before But with Regarding the value type question, I assume if we used In other words assume |
Beta Was this translation helpful? Give feedback.
-
That is correct, and as the language has been evolving towards that direction, I believe it's safe to say, such proposals like
Yeah, that would be the most sensible design. You still have the |
Beta Was this translation helpful? Give feedback.
-
Legacy. Doing it again, i would absolutely not do that. |
Beta Was this translation helpful? Give feedback.
-
Sure. But it postdates our belief that null-tolerance is not a virtue, which is why alter versions of the language embraced explicit null-handling as the mechanism to use, not being null-tolerant. |
Beta Was this translation helpful? Give feedback.
-
Sure. But i don't. And i'd much rather just say: write
Correct. |
Beta Was this translation helpful? Give feedback.
-
Yeah ok. Well like I said I can live with I still would let
Interesting! A true believer then. :) |
Beta Was this translation helpful? Give feedback.
-
In the meantime we have .Net 8. Wouldn't that also be an option for this I could imagine something like this: class Foo
{
public async Task DoAsync() { }
public async Task<int> GetValueAsync() => 42;
}
// usage
Foo? foo = null;
await foo?.DoAsync(); // simply does nothing - no exception is thrown
await foo?.GetValueAsync(); // simply does nothing - no exception is thrown
var x = await foo?.GetValueAsync(); // an ArgumentOutOfRange is thrown Same behaviour for |
Beta Was this translation helpful? Give feedback.
-
What you're saying is that the nullability annotation of the type should control whether the task is allowed to be null and thus suppress throwing the null exception. Keep in mind that |
Beta Was this translation helpful? Give feedback.
-
No. My example code should only illustrate how But the compiler "knows" whether the task has a return value or not. If not no exception should be thrown. If the result value is used in any way (assignment to variable, forward to using, ...) an I edited my sample code to be more clear. |
Beta Was this translation helpful? Give feedback.
-
This would silently change the behavior of existing programs and affect the return type of the expression. I don't think that would be palatable. The |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Btw: |
Beta Was this translation helpful? Give feedback.
-
To my surprise there are many hits for await on conditional member access most of which are invalid? https://github.com/search?q=%2Fawait%5Cs%2B%5Cw%2B%5C%3F%5C.%5Cw%2B%2F%20lang%3AC%23&type=code (not a comprehensive query) Would it make sense if the compiler adds a null check in the presence of a null-conditional access with the current syntax? It seems like that's sort of the expected behavior given that many hits. (edit: actually there's already a null check there, it could only await on the success branch, so it still throws on a null task) |
Beta Was this translation helpful? Give feedback.
-
It's interesting this popped up again as I'm in the middle of converting large amounts of APIs from sync to async in one of our largest solutions. I must say this is super dangerous when performing such conversions. I've hit a few cases here where we had "callback/event" properties typed as This caused code to change from this: myClass.SomeCallback?.Invoke(); To this: if (myClass.SomeCallback is not null and var someCallback)
{
await someCallback.Invoke(cancellationToken);
} But that was after I had made the changes and before sending the PR for review that I noticed there could be a problem and switched to the explicit defensive check. It would've been nice if "it just worked" and I could keep it like: await someCallback?.Invoke(cancellationToken); Which is exactly what @alrz is proposing:
I'd even go as far as to suggest this null check should be done regardless of the presence of null coalescing operators in the right-hand side. It would make using ((IDisposable?)null)
{
Console.WriteLine("Hello world");
} If |
Beta Was this translation helpful? Give feedback.
-
Design Meetings
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-31.md#await
Beta Was this translation helpful? Give feedback.
All reactions