-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Champion: Simplified parameter null validation code #2145
Comments
Shouldn't this be And also doesn't C# will always warning on nullable reference type from now on? |
No. This feature is for runtime value checking only. It does not affect the type system in any way. Hence the syntax annotation is on the value identifier, not the type.
Nullable reference type checking is an opt-in feature. Also it is designed to warn on potential null references in the code base but it doesn't affect the execution semantics of it. |
Maybe over simple, though 😆 |
Great to see this proposal has resurfaced, and has been championed. I think it an excellent idea but thought it had been lost amongst all the other work going on around NRTs. Thanks, @jaredpar. 👍 |
You say that, but the original idea does put it on the type. |
It's the dammit operator placed on the argument. It makes sense that it is applied to the value rather than the type. |
that logic works only for parameters not property setters (that's why we'd need to invent syntax like |
@Joe4evr If I understand it right (from the jaredpar's comment after mine) this is actually newer feature unrelated to nullable reference type. It just a syntactic sugar to throw exception while nullable reference type is compile time warning |
is this feature supposed to ship with 8.0 as well? |
@Logerfo, my money is on "unlikely, but not impossible". Seems a candidate for 8.1+ to me. But I could be wrong... |
In isolation I think this is great but I'd much rather see a more comprehensive contract annotation system with a potential way to expand it for pattern matching validations and potentially even arbitrary code validation as well as return value validations, even if those advancements don't come as part of a first version of this. In short I worry that this:
means something like this will never happen:
|
You're not wrong in that this feature could be achieved without NRTs, but the initial idea of having the compiler generate null checks for parameters (which is an extremely common scenario) came up during discussions of the NRT design. (Or rather, that was the moment the LDT would take it seriously. Theoretically, we could've had a syntax for generated null-checks ages ago.) And while it's true that the check is about the value and not the type, applying the |
I spoke with @tannergooding a little regarding this. He suggested that it might be more sensible for the compiler to emit a call to a helper method to perform the throw instead. I agree with this, but for somewhat different reasons. In my idealized scenario, the null check would actually be emitted by the JIT / codegen, not the C# compiler. The reason for this is that the JIT can generally handle this more optimally since it's already responsible for setting up things like exception dispatch. Consider the following method, whose prologue is quite common throughout the framework. public void DoSomethingWithArrays(byte[] buffer, int offset, int count)
{
if (buffer is null) { /* throw */ }
if ((uint)offset > (uint)buffer.Length || (uint)count > (uint)(buffer.Length - offset)) { /* throw */ }
/* actual method logic goes here */
} With the proposed syntax the method declaration What I would love to see is the JIT elide the check entirely. Instead, optimistically and immediately attempt to dereference mov eax, dword ptr [rcx + 8h] ; rcx := buffer, move buffer.Length into eax If Coming back to the start of this comment, the reason this matters from a compiler perspective is that we should plan for a future JIT which might have these optimizations. There are a handful of ways we could do this: put a special attribute on the arguments, call a helper method to perform the null check, or smuggle some other metadata that allows the JIT to recognize and remove the compiler-emitted checks. I don't really have a vested interest in which particular mechanism is used, but I wanted to make the compiler team aware of the implications of the feature design. |
And what if this method doesn't immediately attempt to use the non-nullable argument and instead does some body of business logic which definitely should not be happening if the argument is null? That seems like a lot of tricky optimization that would only apply to very specific codepaths. |
@HaloFour In most cases I've seen the null check is only meant to avoid a null ref exception on the line immediately following, which generally is a candidate for this optimization. This should serve the majority of developers well by improving the efficiency of their code. Any other more complex code would still go down the standard "perform an explicit null check" path. |
So the specific optimization would only apply to a method accepting a single reference argument that shouldn't be null where the code immediately attempts a deference? Is that really so common? And if it is, wouldn't it be simple enough for the JIT to recognize and optimize without requiring a new special handshake from the compiler? I'm not making the argument against making things faster for a common case, especially if it's common in the BCL itself, this just seems oddly specific. Even in your use case above, what happens if the C# code validates that |
Rereading your original comment it does sound like you're suggesting that this is something that the JIT could do, aided by the compiler emitting a call to a helper method to throw the exception instead of throwing it directly. If that's the case, sounds good to me. :) |
In this case I think we are fairly safe to change the implementation of the |
I think C# is getting unwieldy, newer features are leading to more and more idiosyncratic syntax and non intuitive behavior, consider the assymetric Index concept and the odd way ref and var behave. The language is becoming more and more inelegant each year. Its time for a new language with a richer grammar, these C based grammars are crippled. |
What does any of that rant have do with this proposal? |
@HaloFour - I'm voicing an opinion on the contrived use of "!" which usually means "not" but will now have another meaning. This is being done because the options are limited due to the inflexible grammar. I then go on to suggest that perhaps a new language with a better more flexible grammar is the way to go rather than performing syntactic acrobatics, for example a grammar that has no reserved words would perhaps offer much greater options for adding new features over time. |
Funny, your rant doesn't mention that at all. Syntax often has multiple meanings in many different languages. Many languages overload the definition of
You're free to start a new language if you want, or use any of the other many, many languages in the ecosystem, .NET or otherwise. But I'd suggest that design discussions for languages that are not C# aren't appropriate for a repo specific for the design of the C# language, nor are you likely to recruit many like-minded individuals. |
Can you please take that discussion elsewhere? It is specifically out of scope and unnecessary in this specific issue. You are free to do any of the following as it suits you:
|
I find it weird that this feature is being championed at a time where another feature makes it almost unnecessary. I believe that a few years from now, when nullable reference types and C# 8 are completely adopted by the BCL and most popular libraries, this type of check will be redundant: why would you want to check for a null argument, when you already know you're being passed a non-null one (unless you're an existing public library method already documented to throw I know about the implementation details of null reference types, and that everyone is still "free" to pass null to non-null arguments. In practice, once everything is correctly annotated, that won't really happen. I don't have any crystal ball, but I already saw a similar effect in a large codebase. Everything in this code base (and I mean every field, property, parameter and return type of every method) is annotated with ReSharper's We never encountered those. With everything annotated and compile-time checked we didn't see any Please, at least consider using an attribute like |
Reference #2153 |
My team has seen the same effect from hundreds of thousands of unit tests, and from Code Contracts. However, we often have developers try and use code in new and exciting ways, like jumping into an assembly from PowerShell or C# Interactive or similar. Plus, a lot of code such as DI ends up going into Reflection and similar. As such, the approach my team is taking is:
Given the above, I personally do still see a use for this, even with a history of correctness proofs. |
On my team, we are currently using Fody NullGuard to automatically generate argument null checks in conjunction with the Implicit Nullability ReSharper add-in, which implements static checking for non-nullable reference types. The nice thing is that with this combination (and a little bit of configuration), NullGuard already knows which method parameters need argument null checks and which ones don't. Switching to the feature of this proposal would require us to explicitly annotate the parameters again with no gain, which feels redundant and cumbersome to me. You might argue that being explicit is better in this case, as you enable a side effect, but if you're embracing non-nullable reference types, the side effect is only the second line of defense (for Reflection calls, type checker insufficiency, etc) and, in my opinion, doesn't deserve syntax of its own. About the proposed syntax, I feel it's too special-purpose and closed for future extension, as it can't be extended to more general contact checking. And finally about the position of the exclamation mark: the proposal argues that it can't be put on the type because it's not part of the type in the type system. But it really could be part of the type system quite easily by making So, |
It's not contrived. People write this code, and the language has very different meanings here. It's fine to have a general intuition on how these things work, but then using that intuition to make strong claims that certain things definitely work the same way is not correct. |
I don't think we'll add a code style flag for it (though I could be wrong), but it would be fairly trivial to write your own syntax analyzer to forbid it. |
I don't think we have any intent on banning this feature. That's really not something we do ( |
Personally, I'd love it if the compiler showed an info/warning/error when it sees code like this: public void Example(User user)
{
// Should show info "Can use !! instead"
if(user == null)
throw new ArgumentNullException(nameof(user));
} That way, I could bump the message up from "info" to "warning" or "error", to enforce style guidelines on new projects 🙂 |
That analyzer already exists in VS, along with a fixer to automatically make the change. |
Oh, great! 🙂 |
To confirm, will this be supported on record types as well? public record Person(string Name!!) Would that result in an ArgumentNullException being thrown if |
Interestingly, this will not throw:
|
Just confirming, this isn't supported for property setters and event accessors, right? |
For this code shouldn 't the compiler show an error on
|
It issues a warning:
|
This feature has been removed from C# 11: https://devblogs.microsoft.com/dotnet/csharp-11-preview-updates/#remove-parameter-null-checking-from-c-11 |
Poll please. I don't want noise makers which are like 1% in any community forcing language team to cancel features actual majority needs. |
I don't think they was just cancel it, it just being removed from C#11 because it not ready yet. It might come back later, but not now |
Honestly the only form of param null checking I can think of are:
|
@jaredpar why not keep this open for further discussion into possibly having this refined for future C# versions instead of closing it so that way more ideas for refinement can flow in? Also just thought of this option as well: public class Example
{
public void Something(string s1 not null, bool b1 not null);
} Where the existing keywords could be used by the compiler to insert an |
This is an issue, which tracked an actual championed proposa/implementation. For a discussion, there should be an actual github discussion for it (which could link to this for example). |
For context, here are the minutes from the two Language Design meetings that lead to the feature being removed: |
@AraHaan we considered that syntax but didn't like it. See the notes in: https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-13.md#parameter-null-checking |
I love that idea! Microsoft Research had these .NET code contracts for testing 14 years ago. Having pre-/postconditions, invariants and parameter null checking with first class language and compiler support would be awesome. This would be one of the biggest and best C# releases yet! |
While the LDM members might not like it, the community might. Same for using contracts to check for nulls. I think having all 3 of them as an option but as an There are devs who would prefer these for null checking:
|
We will not do that. We're not going to add multiple redundant syntaxes because there are lots of different opinions on what people personally prefer for each of these areas. |
Is there an issue/discussion and/or a champion for |
Discussion, yes: #6034 |
@jaredpar Shouldn't this issue be removed from the Right now, it gives the impression that this is implemented and coming again in C# 13. |
@julealgon changed to not planned but generally we don't remove the milestones when closing. |
Specification: https://github.com/dotnet/csharplang/blob/main/proposals/param-nullchecking.md
In short though this allows for standard
null
validation on parameters to be simplified using a small annotation on parameters:LDM history:
The text was updated successfully, but these errors were encountered: