null に関する警告を解決する

ヒント

null許容参照型は初めてですか? 注釈と null 状態分析を理解するには、最初に Null 許容参照型 を読み取ります。 この記事では、機能が有効になっているプロジェクトに警告が表示されていることを前提としています。

特定のコンパイラ エラー コードをお探しですか? null 許容に関する警告の解決リファレンス記事では、各 CS86xx 警告とそれに対応する手法を一覧にしています。

null 許容参照型を有効にすると、コードの動作が注釈と一致しないすべての場所で、コンパイラによって警告が発行されます。 ほとんどの警告は、少数のパターンに分類されます。 パターンを認識すると、通常、修正は次の 5 つの手法のいずれかになります。

  • nullチェックを追加する。
  • ?または!注釈を追加または削除します。
  • null コントラクトを記述する属性を追加します。
  • 変数を正しく初期化します。
  • プロジェクト設定を確認します。

この記事では、代表的な例を使用して各手法について説明します。 目標は、警告を黙らせることではありません。 これは、コンパイラが同じ結論に達するように、コードの null 処理意図を明示的にすることです。

Null 状態: コンパイラが追跡するもの

この手法を見る前に、コンパイラが潜在的な null 状態違反を追跡する方法を理解するのに役立ちます。 コードを読み取ると、コンパイラは各式の null 状態を追跡します。式がコード内のその時点で null されるかどうかについての分析です。 null 状態は、次の 2 つの値のいずれかです。

  • not-null — コンパイラは式がここで null されていないことを証明できます。 チェックなしで安全に使用できます。
  • おそらく null - コンパイラは nullを除外できません。 式をチェックせずに使用すると、警告が生成されます。

コンパイラがコードに従うにつれて、変数の null 状態が変化します。 null を返す可能性があるメソッドは、maybe-null の結果を生成します。 if (x is not null) チェックにより、x ブロック内で if に絞り込まれます。 表示されている警告は、コンパイラが、ある式が maybe-null 状態にあると判断しており、その式を not-null であるかのように使おうとしていることを示しています。 この記事の残りの部分の各手法は、式を使用する前に 式が null でないことを 確認するために必要な情報をコンパイラに提供する異なる方法です。

null チェックを追加する

最も一般的な警告は、 null の逆参照の可能性があります。 コンパイラは変数の null 状態をおそらく null に追跡し、チェックなしで使用される変数を確認しました。

public static int LengthOfMessageUnsafe(string? message)
{
    // Warning CS8602: dereference of a possibly null reference.
    return message.Length;
}

通常、修正は ガード句ですガード句は、メソッドまたはブロックの先頭にあるチェックで、入力が無効な場合に返されるかスローされます。 安全な経路のみが継続されます。 チェックが実行されると、コンパイラは変数の null 状態をセーフ パスの not-null に更新します。

public static int DereferenceFixed(string? message)
{
    if (message is null)
    {
        return 0;
    }

    // No warning: the compiler knows message is not-null on this path.
    return message.Length;
}

パターン マッチング (値の形状をテストする is nullis { } などの式)、 ????= には null チェックが含まれます。

public static int NullOperatorsFix(string? message)
{
    // ?. evaluates to null if message is null; ?? supplies the fallback value.
    int length = message?.Length ?? 0;

    // Pattern matching narrows the type on the matching branch.
    if (message is { Length: > 0 })
    {
        length = message.Length;
    }

    return length;
}

プロパティ パターン{ Length: > 0 }は、messageが null 以外で、そのLength プロパティが 0 より大きい場合にのみ一致するため、コンパイラはmessage ブロック内でif として扱います。 より単純な is not null テストでは、プロパティを検査することなく、同じ null 状態の縮小が生成されます。

演算子の詳しい解説については、Null 演算子を参照してください。

注釈を調整する

また、コードで maybe-null 式を null 非許容の変数に代入すると、コンパイラは警告を出します。 この警告は、次の 2 つのいずれかを意味します。

  • 変数は null 値を許可する必要があります。 その場合は、型に ? を追加します。
  • 式によって null 値が生成されることはありません。 生成した API に注釈を付けます。
public static void AssignmentWarning()
{
    // Warning CS8600: converting null literal or possible null value to non-nullable type.
    string name = Lookup("nobody");
    Console.WriteLine(name);
}

Lookupが null を正当に返す場合は、不足している値を受け入れるように呼び出しサイトを変更します。

public static void AssignmentFixed()
{
    string? name = Lookup("somebody");
    if (name is not null)
    {
        Console.WriteLine(name);
    }
}

Lookupが null を返さない場合は、null 非許容参照型を返すようにシグネチャを変更します。 返される値の null 状態が入力に依存するシナリオについては、null 分析属性に関する次のセクションを参照してください。

null 許容演算子 ! 使用するのは、値が null ではないことを保証できるが、型システムでその保証を表すことができる場合のみです。 ! がある箇所はいずれも、コンパイラがもはや保護してくれない場所なので、チェックを追加するか、ソース API にアノテーションを付けることを優先してください。

null 解析属性を追加する

場合によっては、適切な修正プログラムが呼び出しサイトにありません。 メソッドのシグネチャは入力と出力の関係を正確にキャプチャせず、コンパイラは安全でないコード内で警告を発行します。

public static bool IsPresent(string? text) =>
    !string.IsNullOrEmpty(text);

public static void CallerWithoutAttribute(string? text)
{
    if (IsPresent(text))
    {
        // Warning CS8602: dereference of a possibly null reference.
        // The signature doesn't tell the compiler text is not-null here.
        Console.WriteLine(text.Length);
    }
}

IsPresentの本体は、メソッドがtrueを返すときに引数が null ではないことを証明しますが、シグネチャはそうは言いません。 NULL 許容分析属性を追加して、コントラクトを API の一部にします。

public static bool AttributedIsPresent([NotNullWhen(true)] string? text) =>
    !string.IsNullOrEmpty(text);

public static void CallerWithAttribute(string? text)
{
    if (AttributedIsPresent(text))
    {
        // No warning: the attribute tells the compiler text is not-null.
        Console.WriteLine(text.Length);
    }
}

一般的な属性は次のとおりです。

  • NotNullWhenAttribute — メソッドが指定されたブール値を返す場合、引数は not-null です
  • NotNullIfNotNullAttribute — 名前付き引数が not-null の場合、戻り値は not-null です
  • MemberNotNullAttribute — リストされているメンバーは、メソッドが戻った後は null ではありません
  • DoesNotReturnAttribute — メソッドは通常どおりに戻ることはありません (たとえば、常にスローされます)。

一覧は null 許容の静的解析属性 に記載されています。

null を許容しないメンバーを初期化する

コンストラクター警告は、null 非許容フィールド、プロパティ、または 自動プロパティ ( public string Name { get; set; } などのコンパイラによって生成されたバッキング フィールドを使用するプロパティ) が、null 以外の値を割り当てずにコンストラクターを終了することを意味します。

public class PersonUninitialized
{
    // Warning CS8618: Non-nullable property 'Name' is uninitialized.
    public string Name { get; set; }
}

それに対処するには、いくつかの方法があります。 設計の意図に最も一致するものを選択します。

コンストラクター引数として値を要求します。 プライマリ コンストラクター (型自体で宣言され、本文全体で使用できるパラメーター) またはプロパティを初期化する通常のコンストラクターを使用します。

public class PersonInjected(string name)
{
    public string Name { get; } = name;
}

プロパティを requiredします。 呼び出し元はオブジェクト初期化子を使用して初期化する必要があります({ Property = value }に続くnew構文)。

public class PersonRequired
{
    public required string Name { get; init; }
}

既定値を使用して初期化します。 型に意味のある空の値がある場合は、宣言で初期化します。

public class PersonInitialized
{
    public string Name { get; set; } = "John Doe";
}

ヒント

この手法は、型に真に適切な既定値がある場合にのみ選択します。これは、呼び出し元が使用する有効で完全に機能するインスタンスです。 例としては、空のコレクションが含まれます。 "値なし" として扱う センチネル ( String.Empty"N/A""unknown"-1 などのプレースホルダー値 null) を作成しないでください。これは警告を無音にしますが、すべての呼び出し元はセンチネルについて知って確認する必要があり、型システムは役に立たない。 適切な既定値が存在しない場合は、代わりにプロパティを null 許容にします。

プロパティを null 許容にする。 値が実際に欠落している可能性がある場合は、型を null 許容に変更します。

public class PersonOptional
{
    public string? Name { get; set; }
}

ヘルパー メソッドがメンバーを初期化する場合は、コンパイラがメンバーの呼び出しをクレジットできるように、 MemberNotNullAttribute を使用してヘルパーに注釈を付けます。

プロジェクト設定を確認する

新しい C# プロジェクトでは、既定で null 許容参照型が有効になるため、記述または読み取るほとんどのコードで既に機能が有効になっています。 通常、何も構成する必要はありません。 プロジェクトが有効になっているかどうか、または設定を変更する必要がある場合は、<Nullable>.csproj要素を探します。

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

サポートされている一般的な値は、 enable (新しいプロジェクトの既定値) と disableです。 要素が見つからない場合、プロジェクトは SDK とターゲット フレームワーク セットの既定の設定を使用します。

#nullable ディレクティブを持つファイルの一部に対してのみ null 許容を有効にする必要がある場合、または既存のコードベースを移行するときに部分warningsモードとannotationsモードを使用する必要がある場合は、「Nullable 移行戦略」を参照してください。

次に進む場所

警告がこれらのパターンのいずれにも当てはまらない場合は、null 許容警告の解決 のリファレンス記事に、コンパイラが出力するすべての CS86xx 警告に対する手法が示されています。

既存のコードベースで null 許容参照型を段階的に有効にする移行を計画するには、「 Null 許容移行戦略」を参照してください。