Update param-nullchecking proposal (#5397)
This commit is contained in:
parent
9b15547365
commit
4df3296a0f
|
@ -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.
|
||||
|
Loading…
Reference in New Issue