C の null 値を許容する参照型#Nullable reference types in C#

この機能の目的は次のとおりです。The goal of this feature is to:

  • 変数、パラメーター、または参照型の結果が null であるかどうかを開発者が表すことができるようにします。Allow developers to express whether a variable, parameter or result of a reference type is intended to be null or not.
  • このような変数、パラメーター、および結果がその目的に従って使用されない場合は、警告を表示します。Provide warnings when such variables, parameters and results are not used according to that intent.

インテントの式Expression of intent

言語には既に T? 値型の構文が含まれています。The language already contains the T? syntax for value types. 参照型にこの構文を拡張するのは簡単です。It is straightforward to extend this syntax to reference types.

非修飾参照型の目的は、null 以外にする必要があることを前提としてい T ます。It is assumed that the intent of an unadorned reference type T is for it to be non-null.

Null 許容参照のチェックChecking of nullable references

フロー分析は、null 許容の参照変数を追跡します。A flow analysis tracks nullable reference variables. 分析では、null ではないと判断された場合 (たとえば、チェックや代入の後)、その値は null 以外の参照と見なされます。Where the analysis deems that they would not be null (e.g. after a check or an assignment), their value will be considered a non-null reference.

また、null 許容参照は、後置演算子 ("damnit" 演算子) を使用して非 null として明示的に扱うこともでき x! ます。これは、フロー分析が、開発者が知っている null 以外の状況を確立できない場合に使用します。A nullable reference can also explicitly be treated as non-null with the postfix x! operator (the "damnit" operator), for when flow analysis cannot establish a non-null situation that the developer knows is there.

それ以外の場合、null 許容参照が逆参照されるか、null 以外の型に変換されると、警告が示されます。Otherwise, a warning is given if a nullable reference is dereferenced, or is converted to a non-null type.

からへの変換またはからへの変換では、警告が示され S[] T?[] S?[] T[] ます。A warning is given when converting from S[] to T?[] and from S?[] to T[].

C<S> C<T?> 型パラメーターが共変 ( out ) である場合や、 C<S?> C<T> 型パラメーターが反変 () である場合を除き、からに変換 in する場合を除き、からに変換するときに警告が通知されます。A warning is given when converting from C<S> to C<T?> except when the type parameter is covariant (out), and when converting from C<S?> to C<T> except when the type parameter is contravariant (in).

C<T?>型パラメーターに null 以外の制約がある場合は、に対して警告が示されます。A warning is given on C<T?> if the type parameter has non-null constraints.

Null 以外の参照のチェックChecking of non-null references

Null リテラルが null 以外の変数に割り当てられているか、null 以外のパラメーターとして渡された場合、警告が返されます。A warning is given if a null literal is assigned to a non-null variable or passed as a non-null parameter.

コンストラクターが null 以外の参照フィールドを明示的に初期化しない場合も、警告が示されます。A warning is also given if a constructor does not explicitly initialize non-null reference fields.

Null 以外の参照の配列のすべての要素が初期化されていることを適切に追跡することはできません。We cannot adequately track that all elements of an array of non-null references are initialized. ただし、新しく作成された配列の要素が割り当てられる前に、配列の読み取りまたは書き込みが行われる前に、警告を発行することができます。However, we could issue a warning if no element of a newly created array is assigned to before the array is read from or passed on. これは、ノイズが発生することなく、一般的なケースを処理する可能性があります。That might handle the common case without being too noisy.

で警告を生成するか、 default(T) 単に型として処理するかを決定する必要があり T? ます。We need to decide whether default(T) generates a warning, or is simply treated as being of the type T?.

メタデータ表現Metadata representation

Null 値許容要素は、メタデータで属性として表す必要があります。Nullability adornments should be represented in metadata as attributes. これは、ダウンレベルコンパイラによって無視されることを意味します。This means that downlevel compilers will ignore them.

Null 値を許容する注釈だけを含めるかどうかを判断する必要があります。または、アセンブリ内で null 以外が "on" であるかどうかを示すこともできます。We need to decide if only nullable annotations are included, or there's also some indication of whether non-null was "on" in the assembly.

ジェネリックGenerics

型パラメーターに T null 非許容の制約がある場合、その型パラメーターは、そのスコープ内で null 非許容として扱われます。If a type parameter T has non-nullable constraints, it is treated as non-nullable within its scope.

型パラメーターに制約がない場合、または null 値を許容する制約がある場合、状況は少し複雑になります。これは、対応する型引数が null 許容または null 非許容の どちらか になる可能性があることを意味します。If a type parameter is unconstrained or has only nullable constraints, the situation is a little more complex: this means that the corresponding type argument could be either nullable or non-nullable. そのような場合の安全な方法は、型パラメーターを null 許容型と null 非許容型の 両方 として扱い、いずれかの違反があったときに警告を発することです。The safe thing to do in that situation is to treat the type parameter as both nullable and non-nullable, giving warnings when either is violated.

明示的に null 許容の参照制約を許可するかどうかを検討してください。It is worth considering whether explicit nullable reference constraints should be allowed. ただし、null 値を許容する参照型を使用することは、特定のケース (継承された制約) の制約として 暗黙的 に避けることができないことに注意してください。Note, however, that we cannot avoid having nullable reference types implicitly be constraints in certain cases (inherited constraints).

class制約が null ではありません。The class constraint is non-null. class?が "nullable reference type" を示す有効な null 許容制約であるかどうかを検討できます。We can consider whether class? should be a valid nullable constraint denoting "nullable reference type".

型の推論Type inference

型推論では、貢献する型が null 許容の参照型である場合、結果の型は nullable である必要があります。In type inference, if a contributing type is a nullable reference type, the resulting type should be nullable. 言い換えると、null 性伝達されます。In other words, nullness is propagated.

リテラルが参加式であるかどうかを判断して、null にする必要があるかどうかを検討する必要があり null ます。We should consider whether the null literal as a participating expression should contribute nullness. 現在のところ、値型の場合はエラーが発生しますが、参照型の場合は null がプレーン型に正常に変換されます。It doesn't today: for value types it leads to an error, whereas for reference types the null successfully converts to the plain type.

string? n = "world";
var x = b ? "Hello" : n; // string?
var y = b ? "Hello" : null; // string? or error
var z = b ? 7 : null; // Error today, could be int?

Null ガードガイダンスNull guard guidance

機能として、null 値を許容する参照型を使用すると、開発者はそのインテントを表現し、そのインテントが contradicted の場合にフロー分析を使用して警告を提供できます。As a feature, nullable reference types allow developers to express their intent, and provide warnings through flow analysis if that intent is contradicted. Null ガードが必要かどうかに関してよく寄せられる質問があります。There is a common question as to whether or not null guards are necessary.

Null ガードの例Example of null guard

public void DoWork(Worker worker)
{
    // Guard against worker being null
    if (worker is null)
    {
        throw new ArgumentNullException(nameof(worker));
    }

    // Otherwise use worker argument
}

前の例では、 DoWork 関数はを受け入れ、 Worker それがである可能性があることを防ぎ null ます。In the previous example, the DoWork function accepts a Worker and guards against it potentially being null. 引数がの場合、関数はを worker null DoWork 返し throw ます。If the worker argument is null, the DoWork function will throw. Null 値を許容する参照型では、前の例のコードによってパラメーターが使用 Workerれない という意図があり null ます。With nullable reference types, the code in the previous example makes the intent that the Worker parameter would not be null. 関数が DoWork NuGet パッケージや共有ライブラリなどのパブリック API である場合は、null ガードをそのままにしておく必要があります。If the DoWork function was a public API, such as a NuGet package or a shared library - as guidance you should leave null guards in place. パブリック API として、呼び出し元が渡していないことが保証されるのは、それを防ぐことだけです nullAs a public API, the only guarantee that a caller isn't passing null is to guard against it.

インテントの表現Express intent

前の例をより説得力のある方法として使用すると、パラメーターがに Worker なる可能性がある null ため、null ガードがより適切になります。A more compelling use of the previous example is to express that the Worker parameter could be null, thus making the null guard more appropriate. 次の例で null ガードを削除すると、null を逆参照している可能性があることがコンパイラによって警告されます。If you remove the null guard in the following example, the compiler warns that you may be dereferencing null. に関係なく、両方の null ガードは引き続き有効です。Regardless, both null guards are still valid.

public void DoWork(Worker? worker)
{
    // Guard against worker being null
    if (worker is null)
    {
        throw new ArgumentNullException(nameof(worker));
    }

    // Otherwise use worker argument
}

開発者や開発チームによって完全に制御されているソースコードなどの非パブリック Api の場合、null 許容型の参照型を使用すると、開発者が必要としない null ガードを安全に削除できます。For non-public APIs, such as source code entirely in control by a developer or dev team - the nullable reference types could allow for the safe removal of null guards where the developers can guarantee it is not necessary. この機能は警告に役立ちますが、実行時のコードの実行時にが発生することを保証することはできません NullReferenceExceptionThe feature can help with warnings, but it cannot guarantee that at runtime code execution could result in a NullReferenceException.

重大な変更Breaking changes

Null 以外の警告は、既存のコードでは明らかに互換性に影響する変更であり、オプトインメカニズムを使用する必要があります。Non-null warnings are an obvious breaking change on existing code, and should be accompanied with an opt-in mechanism.

当然ながら、null 許容型からの警告 (前述のように) は、null 値の許容属性が暗黙的である特定のシナリオでは、既存のコードの互換性に影響する変更です。Less obviously, warnings from nullable types (as described above) are a breaking change on existing code in certain scenarios where the nullability is implicit:

  • 制約のない型パラメーターは暗黙的に null 値が許容されるものとして扱われるので、に割り当て object たり、アクセスしたりすると、 ToString 警告が生成されます。Unconstrained type parameters will be treated as implicitly nullable, so assigning them to object or accessing e.g. ToString will yield warnings.
  • 型の推定によって式から null が推測 null される場合、既存のコードで null 非許容型ではなく null 許容型が生成され、新しい警告が発生する可能性があります。if type inference infers nullness from null expressions, then existing code will sometimes yield nullable rather than non-nullable types, which can lead to new warnings.

Null 許容の警告も省略可能である必要があります。So nullable warnings also need to be optional

最後に、既存の API に注釈を追加すると、警告が表示されたユーザーがライブラリをアップグレードしたときに重大な変更が発生します。Finally, adding annotations to an existing API will be a breaking change to users who have opted in to warnings, when they upgrade the library. これは、オプトインまたはオプトアウトする機能にもメリットがあります。「バグを修正する必要がありますが、新しい注釈を処理する準備ができていません」This, too, merits the ability to opt in or out. "I want the bug fixes, but I am not ready to deal with their new annotations"

要約すると、以下をオプトインできる必要があります。In summary, you need to be able to opt in/out of:

  • Null 値を許容する警告Nullable warnings
  • Null 以外の警告Non-null warnings
  • 他のファイルの注釈からの警告Warnings from annotations in other files

オプトインの粒度により、アナライザーに似たモデルが提案されます。ここでは、コードの膨大がプラグマと重大度レベルを選択し、ユーザーが選択できます。The granularity of the opt-in suggests an analyzer-like model, where swaths of code can opt in and out with pragmas and severity levels can be chosen by the user. さらに、ライブラリごとのオプション (「JSON.NET の注釈を無視する準備ができないようにする」) は、コード内で属性として表現される可能性があります。Additionally, per-library options ("ignore the annotations from JSON.NET until I'm ready to deal with the fall out") may be expressible in code as attributes.

オプトイン/移行エクスペリエンスの設計は、この機能の成功と有用性に不可欠です。The design of the opt-in/transition experience is crucial to the success and usefulness of this feature. 次のことを確認する必要があります。We need to make sure that:

  • ユーザーは、必要に応じて、null 値を許容するかどうかを段階的に確認できます。Users can adopt nullability checking gradually as they want to
  • ライブラリ作成者は、顧客の侵入を心配することなく、null 値を許容する注釈を追加できます。Library authors can add nullability annotations without fear of breaking customers
  • このような場合でも、"構成の悪夢" は意味がありません。Despite these, there is not a sense of "configuration nightmare"

改変Tweaks

ローカルでは注釈を使用しないことを検討できますが、割り当てられている内容に従って使用されている ? かどうかを観察するだけで済みます。We could consider not using the ? annotations on locals, but just observing whether they are used in accordance with what gets assigned to them. これを優先しません。私たちは、その意図を明確に表現する必要があると考えています。I don't favor this; I think we should uniformly let people express their intent.

T! xランタイムの null チェックを自動生成するパラメーターの短縮を検討できます。We could consider a shorthand T! x on parameters, that auto-generates a runtime null check.

やなどのジェネリック型の特定のパターンで FirstOrDefault は、 TryGet 特定の状況で既定値が明示的に生成されるため、null 非許容型引数と若干奇妙な動作があります。Certain patterns on generic types, such as FirstOrDefault or TryGet, have slightly weird behavior with non-nullable type arguments, because they explicitly yield default values in certain situations. これらの改善に対応するために、型システムの機能を強化することができます。We could try to nuance the type system to accommodate these better. たとえば、 ? 型引数が既に null 値を許容できる場合でも、制約のない型パラメーターでを許可できます。For instance, we could allow ? on unconstrained type parameters, even though the type argument could already be nullable. これは価値があると思います。 null 許容 型との対話に関連する weirdness につながります。I doubt that it is worth it, and it leads to weirdness related to interaction with nullable value types.

null 許容値型Nullable value types

Null 許容値型に対しても上記のセマンティクスの一部を採用することを検討できます。We could consider adopting some of the above semantics for nullable value types as well.

型の推定については既に説明しました。ここでは int? (7, null) 、エラーを提供するだけでなく、から推論できます。We already mentioned type inference, where we could infer int? from (7, null), instead of just giving an error.

また、フロー分析を null 許容の値型に適用することもできます。Another opportunity is to apply the flow analysis to nullable value types. Null 以外と見なされる場合は、特定の方法 (メンバーアクセスなど) で null 非許容型としてを使用できるようにすることができます。When they are deemed non-null, we could actually allow using as the non-nullable type in certain ways (e.g. member access). これは、戻り値の型に対して 既に 実行できるものが、バック互換性の理由で優先されることを気にする必要があるだけです。We just have to be careful that the things that you can already do on a nullable value type will be preferred, for back compat reasons.