带有泛型的可为空引用的注解

Annotation for nullable reference with generics

给定以下通用 Foo1 函数:

struct Key<T> {}
static readonly Key<double> MyKey = new Key<double>();
T? Foo1<T>(Key<T> key)
{
    return default;
}

天真的 reader 会假设:

var foo1 = Foo1(MyKey);

foo1double? 类型,事实证明编译器正在为 return 类型选择 double。我需要显式添加约束以获得可为空的 return 值:

T? Foo2<T>(Key<T> key) where T : struct // really return a nullable
{
    return default;
}

有人可以解释为什么我的第一个 Foo1 函数中没有提取可空引用 ? 的注释吗?

让我们从一些背景开始:

在 C# 9.0 之前 Foo1 无效。即使在启用了可空引用的 C# 8.0 中:

CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type

Foo2 甚至在 C# 8.0 之前就有效,因为 T? 只有在 T 是一个结构时才有意义,在这种情况下 T? 的类型与 TNullable<T>)。到目前为止,这很简单。

从 C# 8.0 开始引入了可空引用,这引起了一些混乱。从现在开始,T? 可以表示 Nullable<T>T。此版本不允许 T? 无限制,但在您指定 where T : class.

时也允许

如果不使用约束,您必须使用属性来指示 T 可以 null 作为 return 值:

// C# 8.0: Poor man's T?
[return: MaybeNull] T Foo1<T>(Key<T> key) => default;

如果 T 现在是值类型呢?它显然不会在 return 值中将其类型更改为 Nullable<T>。要 return 一个 double? 你的类型参数也必须是 double?,也就是说,MyKey 也必须是一个 Key<double?>.

在C# 9.0中放宽了对T?的限制,现在不需要限制了:

// C# 9.0: this is valid now
T? Foo1<T>(Key<T> key) => default;

但它现在基本上与 C# 8.0 版本的含义相同。如果没有 where T : struct 约束,T?T 的类型相同,因此它只是表示结果可以是 null,这可能会出现在编译器警告中。对于 return 可空值类型,您必须使用 double? 作为通用参数,这也意味着您的 Key 也必须定义一个可空类型:

static readonly Key<double?> MyKey = new Key<double?>();

如果可为空的键在您的情况下没有意义,那么您只能像 Foo2 中那样指定 where T : struct 约束,因此旧规则开始生效:T?T 有不同的类型,其中 T? 表示 Nullable<T>.


更新: Foo1Foo2 之间的主要区别可能更明显,如果你看到他们的 decompiled source:

[System.Runtime.CompilerServices.NullableContext(2)]
private static T Foo1<T>([System.Runtime.CompilerServices.Nullable(new byte[] {
    0,
    1
})] Key<T> key)
{
    return default(T);
}

private static Nullable<T> Foo2<T>(Key<T> key) where T : struct
{
    return null;
}

请注意,Foo1 的 return 类型只是 T 带有一些注释,因此编译器可以发出适当的警告。