可以为 null 的引用类型规范Nullable Reference Types Specification

这是一个正在进行的工作-缺少几个部件或这些部件不完整。This is a work in progress - several parts are missing or incomplete.

此功能添加了两种新类型的可为 null 的类型 (可以为 null 的引用类型和可以为 null 的现有值类型) 为 null 的泛型类型,并引入了静态流分析以实现 null 安全。This feature adds two new kinds of nullable types (nullable reference types and nullable generic types) to the existing nullable value types, and introduces a static flow analysis for purpose of null-safety.

语法Syntax

可以为 null 的引用类型和可以为 null 的类型参数Nullable reference types and nullable type parameters

可以为 null 的引用类型和可以为 null 的类型参数的语法 T? 与可以为 null 的值类型的短格式相同,但没有相应的长格式。Nullable reference types and nullable type parameters have the same syntax T? as the short form of nullable value types, but do not have a corresponding long form.

出于规范的目的,当前 nullable_type 生产被重命名为 nullable_value_type ,并 nullable_reference_type nullable_type_parameter 添加了生产:For the purposes of the specification, the current nullable_type production is renamed to nullable_value_type, and nullable_reference_type and nullable_type_parameter productions are added:

type
    : value_type
    | reference_type
    | nullable_type_parameter
    | type_parameter
    | type_unsafe
    ;

reference_type
    : ...
    | nullable_reference_type
    ;

nullable_reference_type
    : non_nullable_reference_type '?'
    ;

non_nullable_reference_type
    : reference_type
    ;

nullable_type_parameter
    : non_nullable_non_value_type_parameter '?'
    ;

non_nullable_non_value_type_parameter
    : type_parameter
    ;

non_nullable_reference_type nullable_reference_type 必须是不可 null 引用类型 (类、接口、委托或数组) 。The non_nullable_reference_type in a nullable_reference_type must be a nonnullable reference type (class, interface, delegate or array).

non_nullable_non_value_type_parameterIn nullable_type_parameter 必须是不被约束为值类型的类型参数。The non_nullable_non_value_type_parameter in nullable_type_parameter must be a type parameter that isn't constrained to be a value type.

可以为 null 的引用类型和可以为 null 的类型参数不能出现在以下位置:Nullable reference types and nullable type parameters cannot occur in the following positions:

  • 作为基类或接口as a base class or interface
  • 作为的接收方 member_accessas the receiver of a member_access
  • 作为 type 中的 object_creation_expressionas the type in an object_creation_expression
  • 作为 delegate_type 中的 delegate_creation_expressionas the delegate_type in a delegate_creation_expression
  • 作为 type 中的 is_expressioncatch_clausetype_patternas the type in an is_expression, a catch_clause or a type_pattern
  • 作为 interface 完全限定的接口成员名称中的as the interface in a fully qualified interface member name

nullable_reference_typenullable_type_parameter 禁用 的可为 null 的注释上下文中提供警告。A warning is given on a nullable_reference_type and nullable_type_parameter in a disabled nullable annotation context.

class and class? 约束class and class? constraint

class约束具有可为 null 的对应项 class?The class constraint has a nullable counterpart class?:

primary_constraint
    : ...
    | 'class' '?'
    ;

class已启用 的批注上下文) 中 (约束的类型参数必须使用不可 null 引用类型进行实例化。A type parameter constrained with class (in an enabled annotation context) must be instantiated with a nonnullable reference type.

class? (或 class 已禁用 的注释上下文中的类型形参) 可以使用可以为 null 的或不可 null 的引用类型进行实例化。A type parameter constrained with class? (or class in a disabled annotation context) may either be instantiated with a nullable or nonnullable reference type.

class?已禁用 批注上下文中的约束提供了警告。A warning is given on a class? constraint in a disabled annotation context.

notnull 约束notnull constraint

使用约束的类型形参 notnull 不能是可以为 null 的类型 (可以为 null 的值类型、可以为 null 的引用类型或可以为 null 的类型参数) A type parameter constrained with notnull may not be a nullable type (nullable value type, nullable reference type or nullable type parameter).

primary_constraint
    : ...
    | 'notnull'
    ;

default 约束default constraint

default约束可用于方法重写或显式实现,以消除 "可以为 null T? 的类型参数" 与 "可以为 null 的值类型" (Nullable<T>) 。The default constraint can be used on a method override or explicit implementation to disambiguate T? meaning "nullable type parameter" from "nullable value type" (Nullable<T>). 如果缺少 default 约束,则 T? 重写或显式实现中的语法将被解释为 Nullable<T>Lacking the default constraint a T? syntax in an override or explicit implementation will be interpreted as Nullable<T>

请参见https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/unconstrained-type-parameter-annotations.md#default-constraintSee https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/unconstrained-type-parameter-annotations.md#default-constraint

包容性运算符The null-forgiving operator

后修复后的 ! 运算符称为包容性运算符。The post-fix ! operator is called the null-forgiving operator. 它可以应用于 primary_expressionnull_conditional_expression 内:It can be applied on a primary_expression or within a null_conditional_expression:

primary_expression
    : ...
    | null_forgiving_expression
    ;

null_forgiving_expression
    : primary_expression '!'
    ;

null_conditional_expression
    : primary_expression null_conditional_operations_no_suppression suppression?
    ;

null_conditional_operations_no_suppression
    : null_conditional_operations? '?' '.' identifier type_argument_list?
    | null_conditional_operations? '?' '[' argument_list ']'
    | null_conditional_operations '.' identifier type_argument_list?
    | null_conditional_operations '[' argument_list ']'
    | null_conditional_operations '(' argument_list? ')'
    ;

null_conditional_operations
    : null_conditional_operations_no_suppression suppression?
    ;

suppression
    : '!'
    ;

例如:For example:

var v = expr!;
expr!.M();
_ = a?.b!.c;

primary_expressionnull_conditional_operations_no_suppression 必须是可以为 null 的类型。The primary_expression and null_conditional_operations_no_suppression must be of a nullable type.

后缀 ! 运算符没有运行时效果,它的计算结果为基础表达式的结果。The postfix ! operator has no runtime effect - it evaluates to the result of the underlying expression. 其唯一的作用是将表达式的 null 状态更改为 "not null",并限制在使用时给出的警告。Its only role is to change the null state of the expression to "not null", and to limit warnings given on its use.

可以为 null 的编译器指令Nullable compiler directives

#nullable 指令控制可为 null 的批注和警告上下文。#nullable directives control the nullable annotation and warning contexts.

pp_directive
    : ...
    | pp_nullable
    ;

pp_nullable
    : whitespace? '#' whitespace? 'nullable' whitespace nullable_action (whitespace nullable_target)? pp_new_line
    ;

nullable_action
    : 'disable'
    | 'enable'
    | 'restore'
    ;

nullable_target
    : 'warnings'
    | 'annotations'
    ;

#pragma warning 展开指令以允许更改可为 null 的警告上下文:#pragma warning directives are expanded to allow changing the nullable warning context:

pragma_warning_body
    : ...
    | 'warning' whitespace warning_action whitespace 'nullable'
    ;

例如:For example:

#pragma warning disable nullable

可为空上下文Nullable contexts

源代码的每个行都有 可为 null 的注释上下文可以为 null 的警告上下文Every line of source code has a nullable annotation context and a nullable warning context. 这些控件控制是否可为 null 的批注是否有效,以及是否给定了可为空性警告。These control whether nullable annotations have effect, and whether nullability warnings are given. 给定行的批注上下文已 禁用启用The annotation context of a given line is either disabled or enabled. 给定行的警告上下文已 禁用启用The warning context of a given line is either disabled or enabled.

既可在 c # 源代码) 之外的项目级别指定这两个上下文,也可通过预处理器指令在源文件中的任何位置指定 (#nullableBoth contexts can be specified at the project level (outside of C# source code), or anywhere within a source file via #nullable pre-processor directives. 如果未提供任何项目级别设置,则默认情况下,这两个上下文都是 禁用 的。If no project level settings are provided the default is for both contexts to be disabled.

#nullable指令控制源文本中的批注和警告上下文,并优先于项目级设置。The #nullable directive controls the annotation and warning contexts within the source text, and take precedence over the project-level settings.

指令设置上下文 () 它控制后续代码行,直到另一个指令重写它,或直到源文件的结尾。A directive sets the context(s) it controls for subsequent lines of code, until another directive overrides it, or until the end of the source file.

指令的效果如下所示:The effect of the directives is as follows:

  • #nullable disable:将可为 null 的批注和警告上下文设置为 已禁用#nullable disable: Sets the nullable annotation and warning contexts to disabled
  • #nullable enable:将可为 null 的批注和警告上下文设置为 enabled#nullable enable: Sets the nullable annotation and warning contexts to enabled
  • #nullable restore:将可为 null 的批注和警告上下文还原到项目设置#nullable restore: Restores the nullable annotation and warning contexts to project settings
  • #nullable disable annotations:将可为 null 的注释上下文设置为 已禁用#nullable disable annotations: Sets the nullable annotation context to disabled
  • #nullable enable annotations:将可为 null 的注释上下文设置为 enabled#nullable enable annotations: Sets the nullable annotation context to enabled
  • #nullable restore annotations:将可为 null 的注释上下文还原到项目设置#nullable restore annotations: Restores the nullable annotation context to project settings
  • #nullable disable warnings:将可为 null 的警告上下文设置为 已禁用#nullable disable warnings: Sets the nullable warning context to disabled
  • #nullable enable warnings:将可为 null 的警告上下文设置为 已启用#nullable enable warnings: Sets the nullable warning context to enabled
  • #nullable restore warnings:将可为 null 的警告上下文还原到项目设置#nullable restore warnings: Restores the nullable warning context to project settings

类型为 Null 性Nullability of types

给定的类型可以具有以下三个 nullabilities 之一: 在意不可 null可以为 nullA given type can have one of three nullabilities: oblivious, nonnullable, and nullable.

如果将可能的值分配给 不可 null 类型,则可能会引发警告 nullNonnullable types may cause warnings if a potential null value is assigned to them. 但是,在意 和可以 null 的类型为 "可 赋值",可以 null 为其分配值而不会出现警告。Oblivious and nullable types, however, are "null-assignable" and can have null values assigned to them without warnings.

可以取消引用或分配 在意不可 null 类型的值,而不会出现警告。Values of oblivious and nonnullable types can be dereferenced or assigned without warnings. 但是, 可以为 null 的类型的值为 " 值",并可能在取消引用或赋值时导致警告,而不进行正确的 null 检查。Values of nullable types, however, are "null-yielding" and may cause warnings when dereferenced or assigned without proper null checking.

Null 产生类型的 默认 null 状态 是 "可能是 null" 或 "可能是默认值"。The default null state of a null-yielding type is "maybe null" or "maybe default". 非 null 生成类型的默认 null 状态为 "not null"。The default null state of a non-null-yielding type is "not null".

类型的类型和在确定其为 null 性时所发生的可为 null 的注释上下文:The kind of type and the nullable annotation context it occurs in determine its nullability:

  • 不可 null 值类型 S 始终为 不可 nullA nonnullable value type S is always nonnullable
  • 可以为 null 的值类型 S? 始终 可以为 nullA nullable value type S? is always nullable
  • C禁用 的注释上下文中的批注引用类型为 在意An unannotated reference type C in a disabled annotation context is oblivious
  • C启用 的批注上下文中的批注引用类型为 不可 nullAn unannotated reference type C in an enabled annotation context is nonnullable
  • 可以为 null 的引用类型可以 C? 为 null (但在 禁用 的批注上下文中可能生成警告) A nullable reference type C? is nullable (but a warning may be yielded in a disabled annotation context)

类型参数还会考虑它们的约束:Type parameters additionally take their constraints into account:

  • T如果任何) 为可以为 null 的类型或 class? 约束 可以为 null ,则为所有约束都 (的类型参数A type parameter T where all constraints (if any) are either nullable types or the class? constraint is nullable
  • 一个类型参数 T ,其中至少有一个约束为 在意不可 null ,或者其中一个 structclassnotnull 约束为A type parameter T where at least one constraint is either oblivious or nonnullable or one of the struct or class or notnull constraints is
    • 禁用 的注释上下文中的 在意oblivious in a disabled annotation context
    • 已启用 的批注上下文中的 不可 nullnonnullable in an enabled annotation context
  • 可以为 null 的类型参数 T? 可以为 null,但如果不是值类型,则 已禁用 的批注上下文中会生成一个警告。 TA nullable type parameter T? is nullable, but a warning is yielded in a disabled annotation context if T isn't a value type

在意 vs 不可 nullOblivious vs nonnullable

type当类型的最后一个标记在该上下文中时,将被视为在给定的批注上下文中发生。A type is deemed to occur in a given annotation context when the last token of the type is within that context.

源代码中的给定引用类型 C 是解释为在意还是不可 null 依赖于源代码的注释上下文。Whether a given reference type C in source code is interpreted as oblivious or nonnullable depends on the annotation context of that source code. 但一旦建立,就会将其视为该类型的一部分,并在替换泛型类型参数的过程中将其视为 "随之一起移动"。But once established, it is considered part of that type, and "travels with it" e.g. during substitution of generic type arguments. 这就像在类型上有一个批注 ? ,但不可见。It is as if there is an annotation like ? on the type, but invisible.

约束Constraints

可以为 null 的引用类型可用作泛型约束。Nullable reference types can be used as generic constraints.

class? 表示 "可能为 null 的引用类型" 的新约束,而 class 启用 的批注上下文中表示 "不可 null 引用类型"。class? is a new constraint denoting "possibly nullable reference type", whereas class in an enabled annotation context denotes "nonnullable reference type".

default 一个新约束,用于表示未知类型参数或值类型。default is a new constraint denoting a type parameter that isn't known to be a reference or value type. 它只能用于重写和显式实现的方法。It can only be used on overridden and explicitly implemented methods. 对于此约束, T? 表示可以为 null 的类型参数,而不是的简写形式 Nullable<T>With this constraint, T? means a nullable type parameter, as opposed to being a shorthand for Nullable<T>.

notnull 表示不可 null 类型参数的新约束。notnull is a new constraint denoting a type parameter that is nonnullable.

类型参数或约束的为空性并不会影响类型是否满足约束,但目前已 (可以为 null 的值类型不满足约束) 的情况除外 structThe nullability of a type argument or of a constraint does not impact whether the type satisfies the constraint, except where that is already the case today (nullable value types do not satisfy the struct constraint). 但是,如果类型参数不满足约束的为 null 性要求,则可以提供警告。However, if the type argument does not satisfy the nullability requirements of the constraint, a warning may be given.

Null 状态和 null 跟踪Null state and null tracking

给定源位置中的每个表达式都具有 null 状态,指示其是否被认为可能计算为 null。Every expression in a given source location has a null state, which indicated whether it is believed to potentially evaluate to null. Null 状态为 "not null"、"可能为 null" 或 "可能为默认值"。The null state is either "not null", "maybe null", or "maybe default". Null 状态用于确定是否应为不安全的转换和取消引用提供警告。The null state is used to determine whether a warning should be given about null-unsafe conversions and dereferences.

"可能为 null" 和 "可能为默认值" 之间的区别非常微妙,适用于类型参数。The distinction between "maybe null" and "maybe default" is subtle and applies to type parameters. 区别在于, T 状态为 "可能为 null" 的类型参数意味着该值在合法值的域中, T 但合法值可能包括 nullThe distinction is that a type parameter T which has the state "maybe null" means the value is in the domain of legal values for T however that legal value may include null. 如果 "可能为默认值",则表示该值可能在的合法域范围之外 TWhere as a "maybe default" means that the value may be outside the legal domain of values for T.

示例:Example:

// The value `t` here has the state "maybe null". It's possible for `T` to be instantiated
// with `string?` in which case `null` would be within the domain of legal values here. The 
// assumption though is the value provided here is within the legal values of `T`. Hence 
// if `T` is `string` then `null` will not be a value, just as we assume that `null` is not
// provided for a normal `string` parameter
void M<T>(T t)
{
    // There is no guarantee that default(T) is within the legal values for T hence the 
    // state *must* be "maybe-default" and hence `local` must be `T?`
    T? local = default(T);
}

变量的 Null 跟踪Null tracking for variables

对于表示变量、字段或属性的某些表达式,将基于对它们的赋值、对它们执行的测试以及它们之间的控制流,在发生的情况之间跟踪 null 状态。For certain expressions denoting variables, fields or properties, the null state is tracked between occurrences, based on assignments to them, tests performed on them and the control flow between them. 这类似于为变量跟踪明确赋值的方式。This is similar to how definite assignment is tracked for variables. 跟踪的表达式如下所示:The tracked expressions are the ones of the following form:

tracked_expression
    : simple_name
    | this
    | base
    | tracked_expression '.' identifier
    ;

其中,标识符表示字段或属性。Where the identifiers denote fields or properties.

被跟踪变量的 null 状态在无法访问的代码中为 "not null"。The null state for tracked variables is "not null" in unreachable code. 这遵循了有关无法访问的代码的其他决策,如考虑将所有局部变量明确赋值。This follows other decisions around unreachable code like considering all locals to be definitely assigned.

描述类似于明确赋值的空状态转换Describe null state transitions similar to definite assignment

表达式为 NullNull state for expressions

表达式的 null 状态派生自其形式和类型以及它所涉及的变量的 null 状态。The null state of an expression is derived from its form and type, and from the null state of variables involved in it.

文本Literals

文本的 null 状态 null 取决于表达式的目标类型。The null state of a null literal depends on the target type of the expression. 如果目标类型是受引用类型约束的类型参数,则它是 "可能的默认值"。If the target type is a type parameter constrained to a reference type then it's "maybe default". 否则为 "可能为 null"。Otherwise it is "maybe null".

文本的 null 状态 default 取决于文本的目标类型 defaultThe null state of a default literal depends on the target type of the default literal. default目标类型的文本与 T 表达式具有相同的 null 状态 default(T)A default literal with target type T has the same null state as the default(T) expression.

任何其他文本的 null 状态为 "not null"。The null state of any other literal is "not null".

简单名称Simple names

如果 simple_name 未归类为值,则其 null 状态为 "not null"。If a simple_name is not classified as a value, its null state is "not null". 否则为跟踪的表达式,其 null 状态将为其在此源位置跟踪的 null 状态。Otherwise it is a tracked expression, and its null state is its tracked null state at this source location.

成员访问Member access

如果 member_access 未归类为值,则其 null 状态为 "not null"。If a member_access is not classified as a value, its null state is "not null". 否则,如果是跟踪的表达式,则其 null 状态将为其在此源位置跟踪的 null 状态。Otherwise, if it is a tracked expression, its null state is its tracked null state at this source location. 否则,其 null 状态为其类型的默认 null 状态。Otherwise its null state is the default null state of its type.

var person = new Person();

// The receiver is a tracked expression hence the member_access of the property 
// is tracked as well 
if (person.FirstName is not null)
{
    Use(person.FirstName);
}

// The return of an invocation is not a tracked expression hence the member_access
// of the return is also not tracked
if (GetAnonymous().FirstName is not null)
{
    // Warning: Cannot convert null literal to non-nullable reference type.
    Use(GetAnonymous().FirstName);
}

void Use(string s) 
{ 
    // ...
}

public class Person
{
    public string? FirstName { get; set; }
    public string? LastName { get; set; }

    private static Person s_anonymous = new Person();
    public static Person GetAnonymous() => s_anonymous;
}

调用表达式Invocation expressions

如果 invocation_expression 调用使用一个或多个特性声明的成员以实现特殊的 null 行为,则 null 状态由这些特性决定。If an invocation_expression invokes a member that is declared with one or more attributes for special null behavior, the null state is determined by those attributes. 否则,表达式的 null 状态为其类型的默认 null 状态。Otherwise the null state of the expression is the default null state of its type.

invocation_expression编译器不跟踪的 null 状态。The null state of an invocation_expression is not tracked by the compiler.


// The result of an invocation_expression is not tracked
if (GetText() is not null)
{
    // Warning: Converting null literal or possible null value to non-nullable type.
    string s = GetText();
    // Warning: Dereference of a possibly null reference.
    Use(s);
}

// Nullable friendly pattern
if (GetText() is string s)
{
    Use(s);
}

string? GetText() => ... 
Use(string s) {  }

元素访问Element access

如果 element_access 调用使用一个或多个特性声明的索引器来实现特殊的 null 行为,则 null 状态由这些特性决定。If an element_access invokes an indexer that is declared with one or more attributes for special null behavior, the null state is determined by those attributes. 否则,表达式的 null 状态为其类型的默认 null 状态。Otherwise the null state of the expression is the default null state of its type.

object?[] array = ...;
if (array[0] != null)
{
    // Warning: Converting null literal or possible null value to non-nullable type.
    object o = array[0];
    // Warning: Dereference of a possibly null reference.
    Console.WriteLine(o.ToString());
}

// Nullable friendly pattern
if (array[0] is {} o)
{
    Console.WriteLine(o.ToString());
}

基本访问权限Base access

如果 B 表示封闭类型的基类型, base.I 则具有与相同的 null 状态, ((B)this).I 并且与 base[E] 具有相同的 null 状态 ((B)this)[E]If B denotes the base type of the enclosing type, base.I has the same null state as ((B)this).I and base[E] has the same null state as ((B)this)[E].

默认表达式Default expressions

default(T) 对于类型的属性,具有 null 状态 Tdefault(T) has the null state based on the properties of the type T:

  • 如果类型是 不可 null 类型,则它的状态为 "not null"。If the type is a nonnullable type then it has the null state "not null"
  • 如果类型是类型参数,则为; 否则,它的状态为 "可能为默认值"Else if the type is a type parameter then it has the null state "maybe default"
  • 否则它的状态为 "可能为 null"Else it has the null state "maybe null"

空条件表达式?。Null-conditional expressions ?.

null_conditional_expression基于表达式类型,具有 null 状态。A null_conditional_expression has the null state based on the expression type. 请注意,这是指的类型 null_conditional_expression ,而不是所调用的成员的原始类型:Note that this refers to the type of the null_conditional_expression, not the original type of the member being invoked:

  • 如果类型是 可以为 null 的值类型,则它的状态为 "可能为 null"If the type is a nullable value type then it has the null state "maybe null"
  • 否则,如果该类型是 可以为 null 的类型参数,则它将具有 null 状态 "可能是默认值"Else if the type is a nullable type parameter then it has the null state "maybe default"
  • 否则它的状态为 "可能为 null"Else it has the null state "maybe null"

强制转换表达式Cast expressions

如果强制转换表达式 (T)E 调用用户定义的转换,则该表达式的 null 状态为用户定义的转换类型的默认 null 状态。If a cast expression (T)E invokes a user-defined conversion, then the null state of the expression is the default null state for the type of the user-defined conversion. 否则:Otherwise:

  • 如果 T不可 null 值类型,则 T 具有 null 状态 "not null"If T is a nonnullable value type then T has the null state "not null"
  • 否则 T ,如果是 可以为 null 的值类型,则 T 具有 null 状态 "可能为 null"Else if T is a nullable value type then T has the null state "maybe null"
  • 否则,如果 T 是形式为 null 的类型, U? 其中 U 为类型参数,则为 T null 状态 "可能是默认值"Else if T is a nullable type in the form U? where U is a type parameter then T has the null state "maybe default"
  • 否则 T ,如果是一个 可以为 null 的类型,并且 E 具有 null 状态 "可能是 null" 或 "可能是默认值",则将其 T 状态设置为 null。Else if T is a nullable type, and E has null state "maybe null" or "maybe default", then T has the null state "maybe null"
  • 否则 T ,如果是类型参数,并且 E 具有 null 状态 "可能是 null" 或 "可能是默认值",则将其 T 状态设置为 null 状态 "可能是默认值"Else if T is a type parameter, and E has null state "maybe null" or "maybe default", then T has the null state "maybe default"
  • Else T 的 null 状态与相同 EElse T has the same null state as E

一元运算符和二元运算符Unary and binary operators

如果一元运算符或二元运算符调用用户定义的运算符,则该表达式的 null 状态为用户定义的运算符的类型的默认 null 状态。If a unary or binary operator invokes an user-defined operator then the null state of the expression is the default null state for the type of the user-defined operator. 否则为表达式的 null 状态。Otherwise it is the null state of the expression.

对于字符串和委托,二进制文件的特殊操作是什么 +Something special to do for binary + over strings and delegates?

Await 表达式Await expressions

的 null 状态 await E 为其类型的默认 null 状态。The null state of await E is the default null state of its type.

as 运算符The as operator

表达式的 null 状态 E as T 首先依赖于该类型的属性 TThe null state of an E as T expression depends first on properties of the type T. 如果的类型 T不可 null ,则 null 状态为 "not null"。If the type of T is nonnullable then the null state is "not null". 否则,null 状态取决于从类型到类型的转换 E TOtherwise the null state depends on the conversion from the type of E to type T:

  • 如果转换为标识、装箱、隐式引用或隐式可为 null 的转换,则 null 状态为 null 状态 EIf the conversion is an identity, boxing, implicit reference, or implicit nullable conversion, then the null state is the null state of E
  • 否则 T ,如果是类型参数,则它的状态为 "可能是默认值"Else if T is a type parameter then it has the null state "maybe default"
  • 否则它的状态为 "可能为 null"Else it has the null state "maybe null"

Null 合并运算符The null-coalescing operator

的 null 状态 E1 ?? E2 为的空状态 E2The null state of E1 ?? E2 is the null state of E2

条件运算符The conditional operator

的 null 状态 E1 ? E2 : E3 基于和的 null 状态 E2 E3The null state of E1 ? E2 : E3 is based on the null state of E2 and E3:

  • 如果两者都为 "not null",则 null 状态为 "not null"。If both are "not null" then the null state is "not null"
  • 否则,如果两者都为 "可能是默认值",则 null 状态为 "可能是默认值"Else if either is "maybe default" then the null state is "maybe default"
  • 否则,null 状态为 "not null"Else the null state is "not null"

查询表达式Query expressions

查询表达式的 null 状态为其类型的默认 null 状态。The null state of a query expression is the default null state of its type.

此处需要其他工作Additional work needed here

赋值运算符Assignment operators

E1 = E2E1 op= E2 都具有与 E2 应用任何隐式转换相同的空状态。E1 = E2 and E1 op= E2 have the same null state as E2 after any implicit conversions have been applied.

传播 null 状态的表达式Expressions that propagate null state

(E)``checked(E)unchecked(E) 都具有与相同的 null 状态 E(E), checked(E) and unchecked(E) all have the same null state as E.

从不为 null 的表达式Expressions that are never null

以下表达式格式的 null 状态始终为 "not null":The null state of the following expression forms is always "not null":

  • this accessthis access
  • 内插字符串interpolated strings
  • new 表达式 (对象、委托、匿名对象和数组创建表达式) new expressions (object, delegate, anonymous object and array creation expressions)
  • typeof 表达式typeof expressions
  • nameof 表达式nameof expressions
  • 匿名函数 (匿名方法和 lambda 表达式) anonymous functions (anonymous methods and lambda expressions)
  • 包容性表达式null-forgiving expressions
  • is 表达式is expressions

嵌套函数Nested functions

嵌套函数 (lambda 和局部函数) 被视为方法,但其捕获变量除外。Nested functions (lambdas and local functions) are treated like methods, except in regards to their captured variables. Lambda 或局部函数内捕获的变量的初始状态是该嵌套函数或 lambda 的所有 "使用" 中的变量的可以为 null 的状态的交集。The initial state of a captured variable inside a lambda or local function is the intersection of the nullable state of the variable at all the "uses" of that nested function or lambda. 本地函数的使用是对该函数的调用,或将其转换为委托的位置。A use of a local function is either a call to that function, or where it is converted to a delegate. Lambda 的使用是在源中定义它的点。A use of a lambda is the point at which it is defined in source.

类型推理Type inference

可以为 null 的隐式类型的局部变量nullable implicitly typed local variables

var 推理引用类型的批注类型和不被约束为值类型的类型参数。var infers an annotated type for reference types, and type parameters that aren't constrained to be a value type. 例如:For instance:

  • 在中, var s = ""; var 将推断为 string?in var s = ""; the var is inferred as string?.
  • 在中, var t = new T(); T 不受约束的将 var 推断为 T?in var t = new T(); with an unconstrained T the var is inferred as T?.

泛型类型推理Generic type inference

泛型类型推理经过了增强,可帮助确定推断的引用类型是否可以为 null。Generic type inference is enhanced to help decide whether inferred reference types should be nullable or not. 这是一个最大努力。This is a best effort. 它可能会生成有关空性约束的警告,并且在将所选重载的推断类型应用于参数时,可能会导致可以为 null 的警告。It may yield warnings regarding nullability constraints, and may lead to nullable warnings when the inferred types of the selected overload are applied to the arguments.

第一阶段The first phase

可以为 null 的引用类型从初始表达式流入边界,如下所述。Nullable reference types flow into the bounds from the initial expressions, as described below. 此外,引入了两种新的界限,即 nulldefaultIn addition, two new kinds of bounds, namely null and default are introduced. 其目的是在输入表达式中执行 nulldefault ,这可能会导致推断出的类型可以为 null,即使在其他情况下也是如此。Their purpose is to carry through occurrences of null or default in the input expressions, which may cause an inferred type to be nullable, even when it otherwise wouldn't. 这甚至适用于可为 null 的值类型,这些 类型得到了增强,可在推断过程中选取 "非 null"。This works even for nullable value types, which are enhanced to pick up "nullness" in the inference process.

在第一阶段中,确定要添加的界限如下:The determination of what bounds to add in the first phase are enhanced as follows:

如果参数 Ei 具有引用类型,则 U 用于推理的类型取决于的 null 状态及其 Ei 声明的类型:If an argument Ei has a reference type, the type U used for inference depends on the null state of Ei as well as its declared type:

  • 如果声明的类型是不可 null 引用类型 U0 或可以为 null 的引用类型, U0?If the declared type is a nonnullable reference type U0 or a nullable reference type U0? then
    • 如果的 null 状态 Ei 为 "not null",则 UU0if the null state of Ei is "not null" then U is U0
    • 如果的 null 状态 Ei 为 "可能为 null",则 UU0?if the null state of Ei is "maybe null" then U is U0?
  • 否则 Ei ,如果具有已声明的类型, U 则为该类型Otherwise if Ei has a declared type, U is that type
  • 否则,如果为,则 Ei null U 为特殊界限 nullOtherwise if Ei is null then U is the special bound null
  • 否则,如果为,则 Ei default U 为特殊界限 defaultOtherwise if Ei is default then U is the special bound default
  • 否则,不进行推理。Otherwise no inference is made.

精确、上限和下限推理Exact, upper-bound and lower-bound inferences

类型推断 U 类型时 V ,如果 V 是可以为 null 的引用类型,则将在 V0? V0 V 以下子句中使用而不是。In inferences from the type U to the type V, if V is a nullable reference type V0?, then V0 is used instead of V in the following clauses.

  • 如果 V 是未固定的类型变量中的一个, U 则会像以前一样,添加一个精确、上下限或下限If V is one of the unfixed type variables, U is added as an exact, upper or lower bound as before
  • 否则,如果 Unulldefault ,则不进行推理Otherwise, if U is null or default, no inference is made
  • 否则,如果 U 是可以为 null 的引用类型 U0? ,则 U0 将在 U 后续子句中使用而不是。Otherwise, if U is a nullable reference type U0?, then U0 is used instead of U in the subsequent clauses.

实质上,与某个未固定的类型变量直接相关的可为 null 将保留在其边界内。The essence is that nullability that pertains directly to one of the unfixed type variables is preserved into its bounds. 另一方面,如果推断将进一步递归到源和目标类型,则会忽略为空性。For the inferences that recurse further into the source and target types, on the other hand, nullability is ignored. 它可以或不匹配,但如果不匹配,则会在以后选择并应用重载时发出警告。It may or may not match, but if it doesn't, a warning will be issued later if the overload is chosen and applied.

修正 Fixing

如果多个边界彼此之间相互转换,但不同,则该规范并不是一种很好的描述。The spec currently does not do a good job of describing what happens when multiple bounds are identity convertible to each other, but are different. 这种情况可能发生 object 在与之间, dynamic 在不同于元素名称的元组类型之间、构造它们的类型之间以及 C C? 引用类型中的类型之间。This may happen between object and dynamic, between tuple types that differ only in element names, between types constructed thereof and now also between C and C? for reference types.

此外,我们还需要将 "非 null" 从输入表达式传播到结果类型。In addition we need to propagate "nullness" from the input expressions to the result type.

为了应对这些情况,我们添加了更多的修复阶段,这现在是:To handle these we add more phases to fixing, which is now:

  1. 将所有边界中的所有类型都作为候选项收集, ? 从所有可为 null 的引用类型中移除Gather all the types in all the bounds as candidates, removing ? from all that are nullable reference types
  2. 根据 (保持和限制) 的精确、下限和上限的要求,消除候选项 null defaultEliminate candidates based on requirements of exact, lower and upper bounds (keeping null and default bounds)
  3. 消除没有隐式转换为所有其他候选项的候选项Eliminate candidates that do not have an implicit conversion to all the other candidates
  4. 如果剩余的候选项彼此之间没有标识转换,则类型推理将失败If the remaining candidates do not all have identity conversions to one another, then type inference fails
  5. 按如下所述 合并 剩余候选项Merge the remaining candidates as described below
  6. 如果生成的候选项是引用类型或不可 null 值类型,并且 所有 完全 限定或下限 都是可以为 null 的值类型、可以为 null 的引用类型 nulldefault ,则 ? 将其添加到生成的候选项,使其成为可以为 null 的值类型或引用类型。If the resulting candidate is a reference type or a nonnullable value type and all of the exact bounds or any of the lower bounds are nullable value types, nullable reference types, null or default, then ? is added to the resulting candidate, making it a nullable value type or reference type.

两个候选类型之间介绍了 合并Merging is described between two candidate types. 它是可传递和可交换的,因此,可按任何顺序将候选项合并,并获得相同的最终结果。It is transitive and commutative, so the candidates can be merged in any order with the same ultimate result. 如果两个候选类型不能相互转换,则它是不确定的。It is undefined if the two candidate types are not identity convertible to each other.

Merge 函数采用两种候选类型和方向 (+-) :The Merge function takes two candidate types and a direction (+ or -):

  • Merge (TTd) = TMerge(T, T, d) = T
  • Merge (ST?+) = merge (S?T+) = merge (ST+) ?Merge(S, T?, +) = Merge(S?, T, +) = Merge(S, T, +)?
  • Merge (ST?-) = merge (S?T-) = merge (ST-) Merge(S, T?, -) = Merge(S?, T, -) = Merge(S, T, -)
  • Merge (C<S1,...,Sn>C<T1,...,Tn>+) = C< merge (S1T1d1) ,..., merge (SnTndn) >其中Merge(C<S1,...,Sn>, C<T1,...,Tn>, +) = C<Merge(S1, T1, d1),...,Merge(Sn, Tn, dn)>, where
    • di = + 如果 i 的第一个类型参数 C<...> 是协变的di = + if the i'th type parameter of C<...> is covariant
    • di = - 如果的 i 第一个类型参数 C<...> 为逆变或固定di = - if the i'th type parameter of C<...> is contra- or invariant
  • Merge (C<S1,...,Sn>C<T1,...,Tn>-) = C< merge (S1T1d1) ,..., merge (SnTndn) >其中Merge(C<S1,...,Sn>, C<T1,...,Tn>, -) = C<Merge(S1, T1, d1),...,Merge(Sn, Tn, dn)>, where
    • di = - 如果 i 的第一个类型参数 C<...> 是协变的di = - if the i'th type parameter of C<...> is covariant
    • di = + 如果的 i 第一个类型参数 C<...> 为逆变或固定di = + if the i'th type parameter of C<...> is contra- or invariant
  • Merge ((S1 s1,..., Sn sn)(T1 t1,..., Tn tn)d) = ( merge (S1T1d) n1,..., 合并 (SnTnd) nn)其中Merge((S1 s1,..., Sn sn), (T1 t1,..., Tn tn), d) = (Merge(S1, T1, d)n1,...,Merge(Sn, Tn, d) nn), where
    • ni 如果 siti 不同,或者两者都不存在,则不存在ni is absent if si and ti differ, or if both are absent
    • nisi siti 是否相同ni is si if si and ti are the same
  • Merge (objectdynamic) = merge (dynamicobject) = dynamicMerge(object, dynamic) = Merge(dynamic, object) = dynamic

警告Warnings

潜在 null 赋值Potential null assignment

潜在的空取消引用Potential null dereference

约束为空性不匹配Constraint nullability mismatch

禁用的注释上下文中可以为 null 的类型Nullable types in disabled annotation context

重写和实现为空性不匹配Override and implementation nullability mismatch

特殊 null 行为的特性Attributes for special null behavior