C経線の許容可能な参照型—null許容参照型への移行—パート1


Cの残忍な機能は、C Count 8で導入された場合は、恐怖に遭遇する可能性を最小限に抑えるSystem.NullReferenceException . null可能性の構文と注釈は、型がnull可能かどうかのヒントを与えます.より良い静的解析は、コードを開発しながら未処理のヌルをキャッチするために利用可能です.何が好きではないですか?
既存のコードベースに明示的nullabilityを導入することはかなりの努力です.何かを散らかすだけではそれ以上のものがある? and ! あなたのコードを通して.それは、銀弾丸ではありません.あなたはまだnull .
一連のブログ記事を通して、私たちはC - Count - Nullabilityについてより多くのことを学び、既存のコードベースを許容できる参照タイプを使用するように移行するときに私のために働いたいくつかのテクニックとアプローチをカバーします.
始めから始めましょう.

参照型、値型、およびNULL


あなたは、ヌルが「10億ドル間違い」であると聞いたかもしれません.2009年に、トニーホア(Algol Wプログラミング言語の作者).apologized for his billion-dollar mistake : NULL参照.彼の最初のゴールはどんなリファレンスを使用するのも確実に安全であると保証することでした.しかし、NULLリファレンスをサポートするのは簡単だったので、彼は無数のエラー、脆弱性、およびシステムクラッシュの多くのシステムでそこにつながる.他の言語はこの考えをコピーしました.そして、おそらく、これまでの10億ドルの痛みと損害を引き起こしているnull参照に終わりました.
参照可能な型は常にCの剰余の一部であった.参照型はリファレンスかNULLのいずれかである.次の例を考えます.
string s = GetValue();
Console.WriteLine($"Length of '{s}': {s.Length}");

s がNULLでない場合、コンソールにメッセージが書き込まれます.しかし、何が起こるかs はNULLですか?正確にNullReferenceException にアクセスするとスローされますLength …の財産null .
チェックを追加できますnull このプロパティにアクセスする前に
string s = GetValue();
Console.WriteLine(s != null
    ? $"Length of '{s}': {s.Length}"
    : "String is null.");

どのように、あなたはそのチェックを必要とするかどうかわかっていますか?If GetValue() 帰らないnull , あなたがチェックする必要があるならば、どのように、あなたは知っていますかs プロパティにアクセスする場合は?
などの値型int , bool , decimal , struct sDateTime またはカスタム実装などNullable<T> これらの値型を“nullable”にし、安全なアクセス方法を持っています.

Note: value types can’t really be null - they are values, not references. With Nullable<T>, you’re wrapping the value to be able to keep track of this additional null state.


変換するコンパイラのマジックがありますint? into Nullable<int> . null可能な値型にアクセスする場合、null または値を含み、チェックを行わなければなりません.ヌルブルでDateTime , 上記の例を次のように書きます.
DateTime? s = GetValue();
Console.WriteLine(s.HasValue
    ? $"The date is: {s.Value:O}"
    : "No date was given.");

ここの重要な違いは意図です.我々とstring 前に、あなたはnull 予定です.それは決して起こりません、それは起こるかもしれませんGetValue() 機能(または安全なアプローチを取り、常に追加)null を参照).値型では、意図はより明確です.エーDateTime 決してありえないnull , に対してNullable<DateTime> それをチェックするnull (or .HasValue ) が必要です.

nullableな参照型は何ですか?


C≧8である.nullable reference types (NRT) 導入され、参考にすることができます意図を伝えるのを助けるnull または、決してnull .
はじめに見たように、参照型が常にNULLになっていることを指摘してみましょう.彼らは、最初のバージョンのCount - Count -string s = null すべての参照型は、常に完全に良いです.
なぜ私はこれを指摘していますか?参照型は永遠にnullableされている今、私たちは、デフォルトで非nullableとしてそれらを考えることによって、そのアイデアをひっくり返しており、構文をnull許容できるように注釈を追加します.注釈は、あなたのコードを書いて、コンパイルしている間、助けますが、ランタイム安全ネットを提供しません.その意味を見てみましょう.
null可能な参照型が有効になっている場合、前の例では、コンパイラがs ないnull (そうでなければ宣言しますstring? s ), そして、NULLチェックを安全に削除できます.
string s = GetValue();
Console.WriteLine($"Length of '{s}': {s.Length}");

string? GetValue() => null;

・・・それともできるのか?驚いたことに、上記のコードをコンパイルして実行することもできますGetValue() Aを返すstring それでnull . あなたは、ちょうどAを見ますNullReferenceException にアクセスするとスローされますLength …の財産null .
では、Cのどれが参照可能なリファレンスタイプですか?私はnullable注釈を呼び出すように- nrtの可能性があることができますあなたのコードに注釈を付ける.
上記のコードをコンパイルしたり、IDEで見たりするときには、アプリケーションを実行したときにあなたを悩ませるかもしれないいくつかの警告が表示されます.
  • nullリテラルまたは可能なnull値をNull null許容型に変換します.
  • CS 8602 -おそらくNULLリファレンスの参照.

  • ありがとう, IDE !おかげで、C .これは実行時にすべて爆破するかもしれませんが、少なくともこのコードを修正するのに十分な警告を得ることができます.そして、その修正は、ほぼ自動的に、迅速な修正を行うことができます.
    Quick-fix to change nullability of a reference type
    これらの修正を適用した後に、我々は基本的に我々の最初の例で戻っています、しかし、今回は我々が予想するもので注釈をつけられます.また、IDEとコンパイラは私たちを助けてくれますGetValue() 非NULLを返す関数string , ツールは我々が冗長をしていることを教えてくれますnull チェックして、削除します.
    string? s = GetValue();
    Console.WriteLine($"Length of '{s}': {s.Length}");
    
    string GetValue() => "";
    
    

    流れ解析


    それがnullabilityとフロー解析に来るいくつかの興味深いものがあります.
    例えば、var は常にNULLとみなされます.C言語英語チームは常に治療することを決めたvar nullableとして、フロー解析に依存して、IDEまたはコンパイラが警告しているかどうかを判断します.
    この例では、s は、GetValue() 関数.
    var s = GetValue();
    Console.WriteLine($"Length of '{s}': {s.Length}");
    
    string GetValue() => "";
    
    
    多くのIDEは、あなたが何を決定するのを助けるインレイタイプヒントをレンダリングしますvar フロー解析に基づいています.簡単な例です.インレイヒントに注意してくださいvar s の許容可能性を変更するGetValue() 関数の変更.また、Squigglyで注意してくださいs.Length , フロー解析がDereferencingかどうか決定するところLength 潜在的にトラブルにあなたを取得する予定です.
    Flow analysis
    クール、ええ?あなたがするとき、ものはより涼しくなりさえしますnull チェック.以下に2つのメソッドを示します:
    void MethodA(string? value) {
        Console.WriteLine(value.Length); // CS8602 - Dereference of a possibly null reference.
    }
    
    void MethodB(string? value) {
        if (value == null) return;
    
        Console.WriteLine(value.Length); // No warning, null check happened before
    }
    
    
    インMethodA() , 我々はしていないnull チェックするvalue パラメータとフロー解析により、null参照を参照することができます.インMethodB() , フロー解析によるとnull チェックして、アクセスしている.Length コードのこの行の可能性がないのでnull リファレンス.

    NULLを許す演算子


    包む前に、もう一つの話題があります.NULLを許す演算子(null抑制演算子、またはDammit演算子とも呼ばれます)! .
    null可能な参照型を使用する場合、式は! IDEとコンパイラの静的フロー解析を指示すると、式のnull状態を「無視」します.
    ここに2つの例があります! は、null解析可能な状態が無視できることをフロー解析に示します.
    string? a = null;
    Console.WriteLine(a!.Length);
    
    string? b = null!;
    Console.WriteLine(b.Length);
    
    
    もちろん、これは理想的ではありませんNullReferenceException ときにアプリを実行します.
    有効なユースケースもあります.フロー解析は、許容可能な値が実際に無効であることを検出できないかもしれません.参照がNULLでないということを知っているならば、それらのケースを抑えることができます.次の例ではIsValidUser() NULLをチェックするので、我々がそうしなければならないNULL警告を抑制することができますConsole.WriteLine コール
    var user = _userManager.GetUserById(id);
    if (IsValidUser(user)) {
        Console.WriteLine($"Username: {user!.Username}");
    }
    
    public static bool IsValidUser(User? user)
        => !string.IsNullOrEmpty(user?.Username);
    
    

    Note: there are better ways to solve some of these suppressions, using special attributes. I’ll cover these in the next post.


    もう一つのケースは、null許容参照タイプをまだ使用しないコードベースを移行するときです.特定のコードパスに対する警告を一時的に抑制し、コードベースの別の部分で作業しているときには、無効にする必要はありません.
    もう一つのケースは、単位テストでありえました.誰かがパスしたらどうなるかチェックしたいnull を参照してください.そのような状況では、あなたはnull! を参照してください.
    NULLを許す演算子は、フロー解析を効果的に無効にし、コードベースで無効なバグを簡単に隠すことができます.注意して使用し、ヌル抑制演算子の使用を考慮してコードの匂い.

    結論


    null可能な参照型が有効になっている場合は、コードを参照して静的フロー解析を改善し、コードを参照する前に変数がnullであるかどうかを判断できます.変数のアノテーションを使用して、変数? 伝えるnull 参照可能です.
    Nullableな参照型は、それがnullabilityになるとき、あなたにランタイム安全性を与えません、しかし、デザイン時間とコンパイル時間ヘルプは素晴らしいです!
    次のポストでは、いくつかのインターナルとNullable注釈コンテキストを見ていきます.