不受约束的类型参数批注

注意

本文是特性规范。 该规范充当该功能的设计文档。 它包括建议的规范更改,以及功能设计和开发过程中所需的信息。 这些文章将发布,直到建议的规范更改最终确定并合并到当前的 ECMA 规范中。

功能规范与已完成的实现之间可能存在一些差异。 这些差异在相关的 语言设计会议(LDM)的说明中得到了体现。

可以在 规范一文中详细了解将功能规范采用 C# 语言标准的过程。

支持者问题:https://github.com/dotnet/csharplang/issues/3297

总结

允许为不受值类型或引用类型限制的类型参数添加可为 null 的注释:T?

static T? FirstOrDefault<T>(this IEnumerable<T> collection) { ... }

? 批注

在 C#8 中,? 批注只能应用于显式限制为值类型或引用类型的类型参数。 在 C#9 中,无论约束如何,都可以将 ? 批注应用于任何类型参数。

除非将类型参数显式限制为值类型,否则批注只能在 #nullable enable 上下文中应用。

如果类型参数 T 被替换为引用类型,则 T? 表示该引用类型的一个可为空的实例。

var s1 = new string[0].FirstOrDefault();  // string? s1
var s2 = new string?[0].FirstOrDefault(); // string? s2

如果 T 替换为值类型,则 T? 表示 T实例。

var i1 = new int[0].FirstOrDefault();  // int i1
var i2 = new int?[0].FirstOrDefault(); // int? i2

如果 T 替换为批注类型 U?,则 T? 表示批注类型 U? 而不是 U??

var u1 = new U[0].FirstOrDefault();  // U? u1
var u2 = new U?[0].FirstOrDefault(); // U? u2

如果 T 替换为类型 U,则 T? 表示 U?,即使在 #nullable disable 上下文中也是如此。

#nullable disable
var u3 = new U[0].FirstOrDefault();  // U? u3

对于返回值,T? 等效于 [MaybeNull]T;对于参数值,T? 等效于 [AllowNull]T。 在重写或实现用 C#8 编译的程序集的接口时,等效性非常重要。

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 约束

在现有代码中,重写和显式实现的泛型方法不能包含显式约束子句,为了与这些代码兼容,重写或显式实现方法中的 T? 被视为 Nullable<T>,其中 T 是值类型。

C#8 允许在重写或显式实现的方法上使用显式 where T : classwhere T : struct 约束,以允许对限制为引用类型的类型参数进行批注。

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 { }
}

若要允许对不受引用类型或值类型约束的类型参数进行批注,C#9 允许新的 where T : default 约束。

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 { }
}

使用 default 约束在方法重写或显式实现以外的情况是错误的。 当重写方法或接口方法中的相应类型参数被约束为引用类型或值类型时,则使用 default 约束是错误的。

设计会议