-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Initial roll out of !! #64720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial roll out of !! #64720
Conversation
Tagging subscribers to this area: @dotnet/area-meta Issue DetailsC# 11 has implemented the parameter null checking feature void M(object arg)
{
if (arg is null)
{
throw new ArgumentNullException(nameof(arg));
}
...
} with: void M(object arg!!)
{
...
} 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
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs
Outdated
Show resolved
Hide resolved
...eLib/src/System/Runtime/InteropServices/CustomMarshalers/EnumeratorToEnumVariantMarshaler.cs
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesCcm.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs
Outdated
Show resolved
Hide resolved
...ibraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanCng.cs
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HashAlgorithm.cs
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SHA256.cs
Outdated
Show resolved
Hide resolved
...ecurity.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.cs
Outdated
Show resolved
Hide resolved
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
db4859d
Compare
@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.
|
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 |
Thanks for the feedback.
That is what it is. Writing: void M(object arg!!)
{
} is the same functionally as: void M(object arg)
{
_ = arg ?? throw new ArgumentNullException(nameof(arg));
} which is the same functionally as: void M(object arg)
{
if (arg is null) throw new ArgumentNullException(nameof(arg));
} The code generated by the C# compiler for public C(List<T> list) : base(list is not null ? list.Count : 0)
{
if (list is null) throw new ArgumentNullException(nameof(list));
...
} now you don't need the second guard, because the validation happens before the public C(List<T> list!!) : base(list.Count)
{
...
} Similarly, consider an async iterator like: public static IAsyncEnumerator<T> WhereAsync(this IAsyncEnumerator<T> source, Func<T, ValueTask<bool>> predicate)
{
if (source is null) throw new ArgumentNullException(nameof(source));
if (predicate is null) throw new ArgumentNullException(nameof(predicate));
return WhereAsyncCore(source, predicate);
static async IAsyncEnumerator<T> WhereAsyncCore(IAsyncEnumerator<T> source, Func<T, ValueTask<bool>> predicate)
{
await foreach (T item in source)
if (await predicate(item))
yield return item;
}
} This employs an outer and an inner method, the outer of which isn't public static async IAsyncEnumerator<T> WhereAsync(this IAsyncEnumerator<T> source!!, Func<T, ValueTask<bool>> predicate!!)
{
await foreach (T item in source)
if (await predicate(item))
yield return item;
} 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: public static Task<int> Choose<T1, T2, T3>(
ISourceBlock<T1> source1, Action<T1> action1,
ISourceBlock<T2> source2, Action<T2> action2,
ISourceBlock<T3> source3, Action<T3> action3,
DataflowBlockOptions dataflowBlockOptions)
{
// Validate arguments
if (source1 == null) throw new ArgumentNullException(nameof(source1));
if (action1 == null) throw new ArgumentNullException(nameof(action1));
if (source2 == null) throw new ArgumentNullException(nameof(source2));
if (action2 == null) throw new ArgumentNullException(nameof(action2));
if (source3 == null) throw new ArgumentNullException(nameof(source3));
if (action3 == null) throw new ArgumentNullException(nameof(action3));
if (dataflowBlockOptions == null) throw new ArgumentNullException(nameof(dataflowBlockOptions)); and now enabling it to look like: public static Task<int> Choose<T1, T2, T3>(
ISourceBlock<T1> source1!!, Action<T1> action1!!,
ISourceBlock<T2> source2!!, Action<T2> action2!!,
ISourceBlock<T3> source3!!, Action<T3> action3!!,
DataflowBlockOptions dataflowBlockOptions!!)
{ 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
9ce3696
Compare
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# :/ |
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: void M([ThrowIfNull] string a) {
} 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 void M(notnull string a) {} |
Also that approach: |
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. |
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 |
Yeah and waiting for this disaster to spill to generic types as well :) I think I'd just choose Perl for readability :) |
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. |
@stephentoub Your Wouldn't it be better to decorate the method itself? Instead of: public static Task<int> Choose<T1, T2, T3>(
ISourceBlock<T1> source1!!, Action<T1> action1!!,
ISourceBlock<T2> source2!!, Action<T2> action2!!,
ISourceBlock<T3> source3!!, Action<T3> action3!!,
DataflowBlockOptions dataflowBlockOptions!!)
{ Maybe? [ValidateArguments]
public static Task<int> Choose<T1, T2, T3>(
ISourceBlock<T1> source1, Action<T1> action1,
ISourceBlock<T2> source2, Action<T2> action2,
ISourceBlock<T3> source3, Action<T3> action3,
DataflowBlockOptions dataflowBlockOptions)
{ Codegen would be the same, of course. I'm probably too late, though? |
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". |
What I'd like would be a simple new compilation flag on a project per project basis: |
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. |
@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 |
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: void M(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. |
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 |
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 |
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 |
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 |
Yeah while I agree, these discussions tend to surface the world anyway, like when Microsoft decided to remove |
Done: |
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.ThrowIfNull
where!!
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