Update param-nullchecking proposal (#5397)

This commit is contained in:
Rikki Gibson 2021-11-10 15:17:49 -08:00 committed by GitHub
parent 9b15547365
commit 4df3296a0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 27 additions and 23 deletions

View File

@ -1,4 +1,4 @@
# Simplified Null Argument Checking
# Parameter Null Checking
## Summary
This proposal provides a simplified syntax for validating method arguments are not `null` and throwing
@ -16,17 +16,17 @@ of it. The syntax can be used independent of `#nullable` directives.
## Detailed Design
### Null validation parameter syntax
The bang operator, `!`, can be positioned after a parameter name in a parameter list and this will cause the C#
compiler to emit standard `null` checking code for that parameter. This is referred to as `null` validation parameter
The bang-bang operator, `!!`, can be positioned after a parameter name in a parameter list and this will cause the C#
compiler to emit `null` checking code for that parameter. This is referred to as `null` validation parameter
syntax. For example:
``` csharp
void M(string name!) {
void M(string name!!) {
...
}
```
Will be translated into:
Will be translated into code similar to the following:
``` csharp
void M(string name) {
@ -37,8 +37,12 @@ void M(string name) {
}
```
The implementation behavior must be that if the parameter is null, it creates and throws an `ArgumentNullException` with the parameter name as a constructor argument. The implementation is free to use any strategy that achieves this. This could result in observable differences between different compliant implementations, such as whether calls to helper methods are present above the call to the method with the null-checked parameter in the exception stack trace.
We make these allowances because parameter null checks are used frequently in libraries with tight performance and size constraints. For example, to optimize code size, inlining, etc., the implementation may use helper methods to perform the null check a la the [ArgumentNullException.ThrowIfNull](https://github.com/dotnet/runtime/blob/1d08e154b942a41e72cbe044e01fff8b13c74496/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs#L56-L69) methods.
The generated `null` check will occur before any developer authored code in the method. When multiple parameters contain
the `!` operator then the checks will occur in the same order as the parameters are declared.
the `!!` operator then the checks will occur in the same order as the parameters are declared.
``` csharp
void M(string p1, string p2) {
@ -53,12 +57,12 @@ void M(string p1, string p2) {
```
The check will be specifically for reference equality to `null`, it does not invoke `==` or any user defined operators.
This also means the `!` operator can only be added to parameters whose type can be tested for equality against `null`.
This also means the `!!` operator can only be added to parameters whose type can be tested for equality against `null`.
This means it can't be used on a parameter whose type is known to be a value type.
``` csharp
// Error: Cannot use ! on parameters who types derive from System.ValueType
void G<T>(T arg!) where T : struct {
// Error: Cannot use !! on parameters who types derive from System.ValueType
void G<T>(T arg!!) where T : struct {
}
```
@ -73,7 +77,7 @@ For example:
``` csharp
class C {
string field = GetString();
C(string name!): this(name) {
C(string name!!): this(name) {
...
}
}
@ -101,7 +105,7 @@ parameter syntax that lacks parens.
``` csharp
void G() {
// An identity lambda which throws on a null input
Func<string, string> s = x! => x;
Func<string, string> s = x!! => x;
}
```
@ -111,7 +115,7 @@ or `async` iterators.
``` csharp
class Iterators {
IEnumerable<char> GetCharacters(string s!) {
IEnumerable<char> GetCharacters(string s!!) {
foreach (var c in s) {
yield return c;
}
@ -124,7 +128,7 @@ class Iterators {
}
```
The `!` operator can only be used for parameter lists which have an associated method body. This
The `!!` operator can only be used for parameter lists which have an associated method body. This
means it cannot be used in an `abstract` method, `interface`, `delegate` or `partial` method
definition.
@ -153,16 +157,16 @@ is instantiated as a value type the code will be evaluated as `false`. For cases
code will do a proper `is null` check.
### Intersection with Nullable Reference Types
Any parameter which has a `!` operator applied to it's name will start with the nullable state being not `null`. This is
Any parameter which has a `!!` operator applied to it's name will start with the nullable state being not `null`. This is
true even if the type of the parameter itself is potentially `null`. That can occur with an explicitly nullable type,
such as say `string?`, or with an unconstrained type parameter.
When a `!` syntax on parameters is combined with an explicitly nullable type on the parameter then a warning will
When a `!!` syntax on parameters is combined with an explicitly nullable type on the parameter then a warning will
be issued by the compiler:
``` csharp
void WarnCase<T>(
string? name!, // Warning: combining explicit null checking with a nullable type
string? name!!, // Warning: combining explicit null checking with a nullable type
T value1 // Okay
)
```
@ -174,7 +178,7 @@ None
### Constructors
The code generation for constructors means there is a small, but observable, behavior change when moving from standard
`null` validation today and the `null` validation parameter syntax (`!`). The `null` check in standard validation
`null` validation today and the `null` validation parameter syntax (`!!`). The `null` check in standard validation
occurs after both field initializers and any `base` or `this` calls. This means a developer can't necessarily migrate
100% of their `null` validation to the new syntax. Constructors at least require some inspection.
@ -183,7 +187,7 @@ logical that the `null` check run before any logic in the constructor does. Can
are discovered.
### Warning when mixing ? and !
There was a lengthy discussion on whether or not a warning should be issued when the `!` syntax is applied to a
There was a lengthy discussion on whether or not a warning should be issued when the `!!` syntax is applied to a
parameter which is explicitly typed to a nullable type. On the surface it seems like a nonsensical declaration by
the developer but there are cases where type hierarchies could force developers into such a situation.
@ -203,7 +207,7 @@ abstract class C2 : C1 {
// Assembly3
abstract class C3 : C2 {
protected override void M(object o!) {
protected override void M(object o!!) {
...
}
}
@ -232,7 +236,7 @@ following to eliminate it:
``` csharp
// Assembly3
abstract class C3 : C2 {
protected override void M(object? o!) {
protected override void M(object? o!!) {
...
}
}
@ -241,15 +245,15 @@ abstract class C3 : C2 {
At this point the author of Assembly3 has a few choices:
- They can accept / suppress the warning about `object?` and `object` mismatch.
- They can accept / suppress the warning about `object?` and `!` mismatch.
- They can just remove the `null` validation check (delete `!` and do explicit checking)
- They can accept / suppress the warning about `object?` and `!!` mismatch.
- They can just remove the `null` validation check (delete `!!` and do explicit checking)
This is a real scenario but for now the idea is to move forward with the warning. If it turns out the warning happens
more frequently than we anticipate then we can remove it later (the reverse is not true).
### Implicit property setter arguments
The `value` argument of a parameter is implicit and does not appear in any parameter list. That means it cannot be a
target of this feature. The property setter syntax could be extended to include a parameter list to allow the `!`
target of this feature. The property setter syntax could be extended to include a parameter list to allow the `!!`
operator to be applied. But that cuts against the idea of this feature making `null` validation simpler. As such the
implicit `value` argument just won't work with this feature.