2019-01-14 22:15:37 +01:00
|
|
|
# Simplified Null Argument Checking
|
2019-01-14 03:16:19 +01:00
|
|
|
|
|
|
|
## Summary
|
|
|
|
This proposal provides a simplified syntax for validating method arguments are not `null` and
|
|
|
|
throwing `ArgumentNullException` appropriately.
|
|
|
|
|
|
|
|
## Motivation
|
|
|
|
The work on designing nullable reference types has caused us to examine the code necessary for
|
|
|
|
null argument validation. Given that NRT doesn't affect code execution developers still must
|
|
|
|
add `if (arg is null) throw` boiler plate code even in projects which are fully null clean. This
|
|
|
|
gave us the desire to explore a minimal syntax for argument `null` validation in the language. While
|
|
|
|
anticipated to pair often with NRT the proposal is independent of it.
|
|
|
|
|
|
|
|
## Detailed Design
|
2019-01-14 22:15:37 +01:00
|
|
|
|
|
|
|
### Null validation
|
2019-01-14 03:16:19 +01:00
|
|
|
The bang operator, `!`, can be positioned after any identifier in a parameter list and this will
|
|
|
|
cause the C# compilet to emit standard `null` checking code for that parameter. For example:
|
|
|
|
|
|
|
|
``` csharp
|
|
|
|
void M(string name!) {
|
|
|
|
...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Will be translated into:
|
|
|
|
|
|
|
|
``` csharp
|
|
|
|
void M(string name!) {
|
|
|
|
if (name is null) {
|
|
|
|
throw new ArgumentNullException(nameof(name));
|
|
|
|
}
|
|
|
|
...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The generated `null` check will occur before any user 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 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
|
|
|
|
have the value `null`. Value types and type parameters not constrained to `class` or `interface`
|
|
|
|
cannot be used here.
|
|
|
|
|
|
|
|
``` csharp
|
|
|
|
// Errro: Cannot use ! on parameter of type T.
|
|
|
|
void G<T>(T arg!) {
|
|
|
|
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-01-14 22:15:37 +01:00
|
|
|
In the case of a constructor the `null` validation will occur before any other code in the constructor. That includes:
|
|
|
|
|
|
|
|
- Chaining to other constructors with `this` or `base`
|
|
|
|
- Field initializers which implicitly occur in the constructor
|
2019-01-14 03:16:19 +01:00
|
|
|
|
|
|
|
``` csharp
|
|
|
|
class C {
|
2019-01-14 22:15:37 +01:00
|
|
|
string field = GetString();
|
|
|
|
C(string name!): this(name) {
|
2019-01-14 03:16:19 +01:00
|
|
|
...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-01-14 22:15:37 +01:00
|
|
|
Will be roughly translated into the following:
|
2019-01-14 03:16:19 +01:00
|
|
|
|
|
|
|
``` csharp
|
|
|
|
class C {
|
2019-01-14 22:15:37 +01:00
|
|
|
C(string name)
|
2019-01-14 03:16:19 +01:00
|
|
|
if (name is null) {
|
|
|
|
throw new ArgumentNullException(nameof(name));
|
|
|
|
}
|
2019-01-14 22:15:37 +01:00
|
|
|
field = GetString();
|
|
|
|
:this(name);
|
2019-01-14 03:16:19 +01:00
|
|
|
...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-01-14 22:15:37 +01:00
|
|
|
Note: this is not legal C# code but instead just an approximation of what the implementation does.
|
|
|
|
|
2019-01-14 03:16:19 +01:00
|
|
|
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.
|
|
|
|
|
2019-01-14 22:15:37 +01:00
|
|
|
### Extending is null
|
|
|
|
The types for which the expression `is null` is valid will be extended to include unconstrained type parameters. This
|
|
|
|
will allow it to fill the intent of checking for `null` on all types which a `null` check is valid. That is types
|
|
|
|
which are not definitely known to be value types. Type parameters which are constrained to `struct` cannot be
|
|
|
|
used with this syntax.
|
|
|
|
|
|
|
|
``` csharp
|
|
|
|
void NullCheck<T1, T2>(T1 p1, T2 p2) where T2 : struct {
|
|
|
|
if (p1 is null) {
|
|
|
|
...
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error
|
|
|
|
if (p2 is null) {
|
|
|
|
...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The behavior of `is null` on a type parameter will be the same as `== null` today. In the cases where the type parameter
|
|
|
|
is instantiated as a value type the code will be evaluated as `false`. For cases where it is a reference type the
|
|
|
|
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
|
|
|
|
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 `!` operator 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
|
|
|
|
T value1 // Okay
|
|
|
|
)
|
|
|
|
```
|
|
|
|
|
2019-01-14 03:16:19 +01:00
|
|
|
## Open Issuess
|
2019-01-14 22:15:37 +01:00
|
|
|
None
|
2019-01-14 03:16:19 +01:00
|
|
|
|
|
|
|
## Considerations
|
2019-01-14 22:15:37 +01:00
|
|
|
|
|
|
|
### 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
|
|
|
|
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.
|
|
|
|
|
|
|
|
After discussion though it was decided that this is very unlikely to cause any significant adoption issues. It's more
|
|
|
|
logical that the `null` check run before any logic in the constructor does. Can revisit if significant compat issues
|
|
|
|
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
|
|
|
|
parameter which is explicitly typed to a nullable type. On the surface it seems like a non-sensical declaration by
|
|
|
|
the developer but there are cases where type hierarchies could force developers into such a situation.
|
|
|
|
|
|
|
|
Consider the following class hierarchy across a series of assemlbies (assuming all are compiled with `null` checking
|
|
|
|
enabled):
|
|
|
|
|
|
|
|
``` csharp
|
|
|
|
// Assembly1
|
|
|
|
abstract class C1 {
|
|
|
|
protected abstract void M(object o);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assembly2
|
|
|
|
abstract class C2 : C1 {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assembly3
|
|
|
|
abstract class C3 : C2 {
|
|
|
|
protected override void M(object o!) {
|
|
|
|
...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Here the author of `C3` decided to add `null` validation to the parameter `o`. This is completely in line with how the
|
|
|
|
feature is intended to be used.
|
|
|
|
|
|
|
|
Now imagine at a later date the author of Assembly2 decides to add the following override:
|
|
|
|
|
|
|
|
``` csharp
|
|
|
|
// Assembly2
|
|
|
|
abstract class C2 : C1 {
|
|
|
|
protected override void M(object? o) {
|
|
|
|
...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
This is allowed by nullable reference types as it's legal to make the contract more flexible for input positions. The
|
|
|
|
NRT feature in general allows for reasonable co/contravariance on parameter / return nullability. However the language
|
|
|
|
does the co/contravariance checking based on the most specific override, not the original declaration. This means the
|
|
|
|
author of Assembly3 will get a warning about the type of `o` not matching and will need to change the signature to the
|
|
|
|
following to eliminate it:
|
|
|
|
|
|
|
|
``` csharp
|
|
|
|
// Assembly3
|
|
|
|
abstract class C3 : C2 {
|
|
|
|
protected override void M(object? o!) {
|
|
|
|
...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
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 `!`
|
|
|
|
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.
|
2019-01-14 03:16:19 +01:00
|
|
|
|
|
|
|
## Future Considerations
|
2019-01-14 22:15:37 +01:00
|
|
|
None
|
|
|
|
|
|
|
|
- iterators
|
|
|
|
- ctors
|
|
|
|
- lambda parameters
|