Uh oh!
There was an error while loading. Please reload this page.
Initial roll out of !! - #64720
Conversation
ghost
commented
Feb 3, 2022
Tagging subscribers to this area: @dotnet/area-meta Issue DetailsC# 11 has implemented the parameter null checking feature voidM(objectarg){if(argisnull){thrownewArgumentNullException(nameof(arg));}
...}with: voidM(objectarg!!){
...}This PR provides an initial pass through dotnet/runtime replacing manual ANE throwing with This was partially done with the new analyzer/fixer in VS, but there are some issues still being worked out with it, so I manually reviewed and fixed up issues in the changes and then also did a lot of greping and manual changes. I'm 100% certain there are a non-trivial number of additional opportunities, which we can follow-up with subsequently, but I wanted to get this initial round in, as it's quite a large change. For the most part I avoided any behavioral changes, e.g. in cases where using I still need to fix up some warnings/errors/test failures here and there. cc: @RikkiGibson, @jaredpar
|
jeffhandley
left a comment
There was a problem hiding this comment.
I reviewed 10% of the files, including some from the top, some from the middle, and some from the bottom. I observed several different patterns, saw a couple that took me a second, but then I understood them, and I saw nothing amiss.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
bartonjs
commented
Feb 3, 2022
Since I don't know if we're going for doc-it or revert-it, I marked all of the exception ordering changes I saw (and, for good measure, one that didn't (oops)). Checked
Aside from the one method on CryptoConfig, LGTM. |
46e1600 to
db4859dComparestephentoub
commented
Feb 5, 2022
@LunarWhisper, @sstregg, @alhimik45, I'm curious about the confused emoji you selected. Is that about the |
This comment was marked as resolved.
This comment was marked as resolved.
Hi! Yes, my reaction refers to the feature itself.
|
alhimik45
commented
Feb 6, 2022
I also was confused by the feature itself as I saw it first time. And it's strange that having the course to the explicitly nullable types devs are bothered by adding null-checking syntax crutch for one special case. It would make sense if |
stephentoub
commented
Feb 7, 2022
Thanks for the feedback.
That is what it is. Writing: voidM(objectarg!!){}is the same functionally as: voidM(objectarg){_=arg??thrownewArgumentNullException(nameof(arg));}which is the same functionally as: voidM(objectarg){if(argisnull)thrownewArgumentNullException(nameof(arg));}The code generated by the C# compiler for publicC(List<T> list):base(listis not null?list.Count:0){if(listisnull)thrownewArgumentNullException(nameof(list));
...}now you don't need the second guard, because the validation happens before the publicC(List<T> list!!):base(list.Count){
...}Similarly, consider an async iterator like: publicstaticIAsyncEnumerator<T>WhereAsync(thisIAsyncEnumerator<T>source,Func<T,ValueTask<bool>>predicate){if(sourceisnull)thrownewArgumentNullException(nameof(source));if(predicateisnull)thrownewArgumentNullException(nameof(predicate));returnWhereAsyncCore(source,predicate);staticasyncIAsyncEnumerator<T>WhereAsyncCore(IAsyncEnumerator<T>source,Func<T,ValueTask<bool>>predicate){awaitforeach(Titeminsource)if(awaitpredicate(item))yieldreturnitem;}}This employs an outer and an inner method, the outer of which isn't publicstaticasyncIAsyncEnumerator<T>WhereAsync(thisIAsyncEnumerator<T>source!!,Func<T,ValueTask<bool>>predicate!!){awaitforeach(Titeminsource)if(awaitpredicate(item))yieldreturnitem;}with exactly the same effect, because the Note that
Obviously smart people are able to disagree on what helps and hurts a situation. FWIW, I think it improves the readability and maintainability of the code. This PR, for example, is removing ~17,000 lines of boilerplate from the repo, taking code that previously looked like: publicstaticTask<int>Choose<T1,T2,T3>(ISourceBlock<T1>source1,Action<T1>action1,ISourceBlock<T2>source2,Action<T2>action2,ISourceBlock<T3>source3,Action<T3>action3,DataflowBlockOptionsdataflowBlockOptions){// Validate argumentsif(source1==null)thrownewArgumentNullException(nameof(source1));if(action1==null)thrownewArgumentNullException(nameof(action1));if(source2==null)thrownewArgumentNullException(nameof(source2));if(action2==null)thrownewArgumentNullException(nameof(action2));if(source3==null)thrownewArgumentNullException(nameof(source3));if(action3==null)thrownewArgumentNullException(nameof(action3));if(dataflowBlockOptions==null)thrownewArgumentNullException(nameof(dataflowBlockOptions));and now enabling it to look like: publicstaticTask<int>Choose<T1,T2,T3>(ISourceBlock<T1>source1!!,Action<T1>action1!!,ISourceBlock<T2>source2!!,Action<T2>action2!!,ISourceBlock<T3>source3!!,Action<T3>action3!!,DataflowBlockOptionsdataflowBlockOptions!!){That's a random example I pulled from this diff. To me, I can much more easily see the intention behind the validation being performed, that all the arguments that are supposed to be null checked are being null checked, and that they're being null checked correctly, e.g. the right argument is being thrown with the right argument name. And on top of that, the use of
If nullable reference types were a thing 20 years ago in C# 1.0 and .NET Framework 1.0, it's quite possible we would have ended up with a solution like you suggest. But nullable reference types didn't come along until just a couple of years ago, are still not widely used, aren't 100% perfect in their validation and representation of reality, and are optional. The vast majority of code consuming the core .NET libraries, for example, is not using nullable reference types (but uses the same core library binaries), but even if that weren't the case, suddenly changing all code that did
Nothing prevents that from happening in addition in the future. Such contract-based schemes were discussed in depth in multiple C# language design team meetings. In any event, I appreciate your taking the time to share your feedback. As this PR is about employing a C# language feature that's already been approved and is already in the compiler, the feedback doesn't really impact the PR. But if after my responses you still have strong feelings about this not being the right shape of the feature, I'd encourage you to open an issue in https://github.com/dotnet/csharplang. Thanks. |
bc13358 to
9ce3696CompareTanvirArjel
commented
Feb 9, 2022
I see this solution as weird and confusing way to properly implement non-nullability... That is inconsistent and uclear way to solve pretty much the same problem which was already solved in completely different way (decorating type and not parameter name). That will also be for sure a lot of confusion for anyone new coming to C# :/ |
Vake93
commented
Feb 9, 2022
My question is about where is this exception thrown from. Is it thrown from inside the method? |
Please stop adding weird symbols for nullability reasons. Everything is already confusing enough with using the same nullability symbols for value and reference types ( The feature itself is fine, but it could be easily added with something like: voidM([ThrowIfNull]stringa){}Which is explicit, and readable... because the next thing you know, we'll be reading the language when loud as questioning and shouting stuff ( This is all making the language completely unreadable (And yes, I understand You could even use voidM(notnullstring a){} |
Runaurufu
commented
Feb 9, 2022
Also that approach: |
ryanbnl
commented
Feb 9, 2022
We're turning C# into Javascript 👎 Is there a reason why this cannot be a compile-time option? You almost always want these guard clauses to be generated, however it can break things in order code bases. So make a compile-time flag which will auto-generate the guard clause for these:
but not for this:
As it's a flag it can also be turned off completely. |
worldpwn
commented
Feb 9, 2022
I think it will be better to pick the same approach as with We have: It should be: This will lead to the same behavior as described but without |
Hermholtz
commented
Feb 9, 2022
Yeah and waiting for this disaster to spill to generic types as well :) I think I'd just choose Perl for readability :) |
iancooper
commented
Feb 9, 2022
I don't think this has good usability. We should not be adding language statements to variable names. The name ought not to indicate anything about the type, the type should. |
jods4
commented
Feb 9, 2022
@stephentoub Your Wouldn't it be better to decorate the method itself? Instead of: publicstaticTask<int>Choose<T1,T2,T3>(ISourceBlock<T1>source1!!,Action<T1>action1!!,ISourceBlock<T2>source2!!,Action<T2>action2!!,ISourceBlock<T3>source3!!,Action<T3>action3!!,DataflowBlockOptionsdataflowBlockOptions!!){Maybe? [ValidateArguments]publicstaticTask<int>Choose<T1,T2,T3>(ISourceBlock<T1>source1,Action<T1>action1,ISourceBlock<T2>source2,Action<T2>action2,ISourceBlock<T3>source3,Action<T3>action3,DataflowBlockOptionsdataflowBlockOptions){Codegen would be the same, of course. I'm probably too late, though? |
JoeStead
commented
Feb 9, 2022
Agreed, we have |
I think a lot of the recent and upcoming C# language features get one fundamental thing wrong (in my personal opinion). They optimise for writing code, not for reading code. I think it should be the opposite. @stephentoub has shown a great number of examples throughout this conversation how Writing a few extra lines of if statements is not that hard and especially in .NET it's a minuscule problem given that most IDEs already write it for me at the tip of a few key strokes followed by a double I would encourage developers at Microsoft to ask themselves how open they actually are to community feedback like this. I don't mean to suggest anything bad, but personally I often get the feeling that by the time a feature like this gets revealed for feedback a lot of investment (time and emotion) has already gone into developing a (more or less polished) prototype of it at which point people already have a personal investment in defending their decisions and have a bias towards downplaying good counter arguments as "minority noise". |
olivier-spinelli
commented
Feb 9, 2022
What I'd like would be a simple new compilation flag on a project per project basis: |
Nihlus
commented
Feb 9, 2022
This is such an odd way of implementing an arguably very useful feature. Why add yet another syntax variant that doesn't jive with existing syntax? Why not allow developers to globally enable it for a project that's already fully annotated with nullability? Why two exclamation marks and not one? Please listen to the community on this one and don't just blindly push it through. It's not the feature that's being questioned, it's the way it's implemented. |
olivier-spinelli
commented
Feb 9, 2022
@Nihlus What about the |
Since the advent of NRT,s it's been like a green light to use more nulls. C# devs seem particularly averse to I also have another comment re. the actual exception type: in a library, like .NET itself, the exception is as meaningul as it can be. However, in client apps and libraries, where things are much more specific, then |
asbjornu
commented
Feb 9, 2022
I don't really understand the design choice here. Nullable types already establish But somehow you concluded that the annotation should go on the argument name and not on the type as well as requiring two exclamation marks instead of one? I think this reads so much better than the proposed implementation: voidM(object!arg){
...}Another option would be to expand on NRT to make the compiler even more strict, adding null-checks to all reference types automatically, avoiding the need for syntactic sugar altogether. |
SteveDunn
commented
Feb 9, 2022
I would like a combination of:
I wrote about validation, invariants, and a modern-day replacement of Code Contracts, here ... and for validation, a language proposal for invariant records |
I agree will be more simple adding in csproj I hope that |
Runaurufu
commented
Feb 9, 2022
I would prefer |
For example, I use the nullable feature. And in my projects, I have nullable only from DB/DataInput all others are considered not null because I make null checks after DB/DatatInput. So in the case of |
javiercampos
commented
Feb 9, 2022
Except they involve different dynamics (also, different for value and reference types). A Also, would this work for This is all too complicated, again, I'd vote for |
80O
commented
Feb 9, 2022
I would rather see nullable being applied to existing source code and have nullable enabled be the default. Adding If I enable nullable in my projects, and something can be null, its |
Prefix |
And we are back in time when NRT and
This is same case as for nullable ref types.. originally things like As for constructs like |
Everyone, thank you for the discussion, but this is not the place for it. This PR is simply using a now-existing C# feature, it is not designing nor implementing that feature in the language or compiler (if the feedback were on how the feature was employed in dotnet/runtime, that would be appropriate here, but that's not what this discussion has become). The C# language design team doesn't regularly pay attention to this repo, nor to specific PRs in this repo, and this is not where the community-at-large expects to find and discuss language features. Feedback on a language feature on a PR in this repo will largely go unseen by the relevant folks. If you would like to provide feedback on C# language design, the right place is https://github.com/dotnet/csharplang. If you feel strongly that |
Hermholtz
commented
Feb 9, 2022
Yeah while I agree, these discussions tend to surface the world anyway, like when Microsoft decided to remove |
dustinmoris
commented
Feb 9, 2022
stephentoub
commented
Feb 9, 2022
Thank you, @dustinmoris. |

C# 11 has implemented the parameter null checking feature
!!, which let's us replace:with:
This PR provides an initial pass through dotnet/runtime replacing manual ANE throwing with
!!where possible, andArgumentNullException.ThrowIfNullwhere!!isn't possible but the method call is.This was partially done with the new analyzer/fixer in VS, but there are some issues still being worked out with it, so I manually reviewed and fixed up issues in the changes and then also did a lot of greping and manual changes. I'm 100% certain there are a non-trivial number of additional opportunities, which we can follow-up with subsequently, but I wanted to get this initial round in, as it's quite a large change.
For the most part I avoided any behavioral changes, e.g. in cases where using
!!would have changed exception order, I avoided doing so in almost all cases (the only cases I went ahead with it were similar to those in #64357, where two parameters were being validated with multiple argument exceptions and this just changes the order of those argument exceptions in some multi-argument-error cases.I still need to fix up some warnings/errors/test failures here and there.
cc: @RikkiGibson, @jaredpar