csharplang/proposals/csharp-9.0/unconstrained-type-parameter-annotations.md

99 lines
3.6 KiB
Markdown

# Unconstrained type parameter annotations
## Summary
Allow nullable annotations for type parameters that are not constrained to value types or reference types: `T?`.
```C#
static T? FirstOrDefault<T>(this IEnumerable<T> collection) { ... }
```
## `?` annotation
In C#8, `?` annotations could only be applied to type parameters that were explicitly constrained to value types or reference types.
In C#9, `?` annotations can be applied to any type parameter, regardless of constraints.
Unless a type parameter is explicitly constrained to value types, annotations can only be applied within a `#nullable enable` context.
If a type parameter `T` is substituted with a reference type, then `T?` represents a nullable instance of that reference type.
```C#
var s1 = new string[0].FirstOrDefault(); // string? s1
var s2 = new string?[0].FirstOrDefault(); // string? s2
```
If `T` is substituted with a value type, then `T?` represents an instance of `T`.
```C#
var i1 = new int[0].FirstOrDefault(); // int i1
var i2 = new int?[0].FirstOrDefault(); // int? i2
```
If `T` is substituted with an annotated type `U?`, then `T?` represents the annotated type `U?` rather than `U??`.
```C#
var u1 = new U[0].FirstOrDefault(); // U? u1
var u2 = new U?[0].FirstOrDefault(); // U? u2
```
If `T` is substituted with a type `U`, then `T?` represents `U?`, even within a `#nullable disable` context.
```C#
#nullable disable
var u3 = new U[0].FirstOrDefault(); // U? u3
```
For return values, `T?` is equivalent to `[MaybeNull]T`;
for argument values, `T?` is equivalent to `[AllowNull]T`.
The equivalence is important when overriding or implementing interfaces from an assembly compiled with C#8.
```C#
public abstract class A
{
[return: MaybeNull] public abstract T F1<T>();
public abstract void F2<T>([AllowNull] T t);
}
public class B : A
{
public override T? F1<T>() where T : default { ... } // matches A.F1<T>()
public override void F2<T>(T? t) where T : default { ... } // matches A.F2<T>()
}
```
## `default` constraint
For compatibility with existing code where overridden and explicitly implemented generic methods could not include explicit constraint clauses, `T?` in an overridden or explicitly implemented method is treated as `Nullable<T>` where `T` is a value type.
To allow annotations for type parameters constrained to reference types, C#8 allowed explicit `where T : class` and `where T : struct` constraints on the overridden or explicitly implemented method.
```C#
class A1
{
public virtual void F1<T>(T? t) where T : struct { }
public virtual void F1<T>(T? t) where T : class { }
}
class B1 : A1
{
public override void F1<T>(T? t) /*where T : struct*/ { }
public override void F1<T>(T? t) where T : class { }
}
```
To allow annotations for type parameters that are not constrained to reference types or value types, C#9 allows a new `where T : default` constraint.
```C#
class A2
{
public virtual void F2<T>(T? t) where T : struct { }
public virtual void F2<T>(T? t) { }
}
class B2 : A2
{
public override void F2<T>(T? t) /*where T : struct*/ { }
public override void F2<T>(T? t) where T : default { }
}
```
It is an error to use a `default` constraint other than on a method override or explicit implementation.
It is an error to use a `default` constraint when the corresponding type parameter in the overridden or interface method is constrained to a reference type or value type.
## Design meetings
- https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-11-25.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-17.md#t