Null安全(Null Safety)の実現
1965年にnull参照が発明されてから、多くの言語にnullが採用されてきた。
nullは便利だが、一方で数々の脆弱性やnull参照によるシステムクラッシュの原因となった。
プログラマーたちはnullが入り込むかもしれないという懸念にいつも悩まされてきた。
そうした背景から、現在ではnull安全(Null Safety)を採用する言語が増えてきつつある。
null安全とは、nullをデフォルトで非許容にしたり、null参照例外が発生しうるコードをコンパイルエラーにする等の仕組みのことである。
この流れを汲み、C#でもVersion8.0にて「null許容参照型」が導入された。
これまでC#では、すべての参照型がNull許容型であった。
しかし、それでは安全性に問題があるため、nullを明示的に許可した場合のみnull許容型にしようというのが「null許容参照型」である。
これまでのように型名のみで宣言された場合は、null非許容となる。
nullを許可したい場合は型名に?サフィックスを付ける。
1 2 |
string str1 = null; //Null非許容 string? str2 = null; //Null許容 |
これは値型にnullを許可する「null許容値型」の書式と同じだが、null許容値型がNullable<T>というもとの型とは全く別の型になるのに対し、参照型の場合はアノテーション(コンパイラが判断するための注釈)にすぎず、内部的には許容型も非許容型も同じ型となる。
Null許容コンテキストと#nullableディレクティブ
C#8.0より前では、参照型に?をつけない状態でnull許容型となっていた。
しかし、8.0では逆に、参照型に?をつけない状態ではnull非許容型となる。
そのためNull非許容がデフォルトとなると、これまでに書かれたコードでは大量の警告が発生してしまう。(下位互換性を保つためにエラーにはならない)
C#は大規模なプロジェクトにも多く用いられる言語であり、このような破壊的変更には非常に慎重であり、下位互換性を保つように配慮されている。
今回のバージョンアップで既存のコードに問題が発生しないように、null許容参照型は既存のコードではオプトイン機能となっており、デフォルトではOFFとなっている。
(オプトイン・・・デフォルトではOFFになっており、明示的に許可することでONとなること)
新規でプロジェクトを作成するとデフォルトでnull許容参照型がONになっている。
この、null許容参照型が有効になっているかどうかを指してnull 許容コンテキストという。
null許容コンテキストをプロジェクト単位で指定するには、プロジェクトファイルの<Nullable>プロパティをenableまたはdisableにする。enableにすると、null許容参照型が有効となる。
また、nullableディレクティブを使い、null許容コンテキストを行単位で細かく切り替えることができる。
nullableディレクティブを大雑把に説明すると以下のようになっている。下の2つは主に既存のコードを段階的にnull許容参照型に移行する過程で使われる。
#nullable disable null許容参照型OFF C#7.3以前の仕様。参照型はすべてnull許容型。
#nullable enable null許容参照型ON C#8.0からの仕様。参照型でnullを使う場合は?が必要。
#nullable restore プロジェクト全体のデフォルトに戻る。
#nullable enable annotations 警告は出したくないが、null許容参照型(?サフィックス)だけ使いたい場合に使用。
#nullable enable warnings 警告だけを出したい場合に使用。
細かい仕様は以下。
#nullable disable
C# 7.3 以前と同様
- Null 許容の警告は無効
- 参照型の変数はすべてnull 許容参照型(未指定のnull許容)
- 参照型の変数にnullを代入できる
- null 許容参照型を宣言できない(?サフィックスの使用不可)
- null 免除演算子
!
を使用しても警告は出ないが、効果はない
#nullable enable
すべての null 参照分析とnul許容参照型の機能が有効になる。
- 新しい null 許容のすべての警告が有効
- ?サフィックスを付けない参照型の変数はすべてnull 非許容参照型
- null非許容型の変数にnullを代入すると警告が出る
- null 許容参照型の宣言ができる(?サフィックスの使用可)
- null 免除演算子!を使用できる。null免除演算子は
null
に割り当てられる可能性があるという警告をださないようにすることができる。
#nullable enable annotations
アノテーション(?サフィックス)のみを許可。警告は出さない。
- null 許容の新しい警告はすべて無効
- null非許容型の変数にnullを代入できる
- null 許容参照型の宣言ができる(?サフィックスの使用が可能)
- ?サフィックスを付けない参照型の変数はすべてnull 非許容参照型
- null 免除演算子
!
を使用しても警告は出ないが、効果はない - コードがnullを逆参照する可能性がある場合も、コンパイラはnull分析を実行せず警告を出さない。
#nullable enable warnings
すべてのnull参照分析を実行し、警告のみを出す。
- 新しい Null 許容のすべての警告が有効。
- null 許容参照型を宣言する
?
サフィックスを使用すると警告がでる。 - 参照型の変数はすべて null にすることができる。 ただし、
?
サフィックスを使用して宣言しない限り、メソッドの左中括弧でメンバーはnullではないとみなされる。 - null 免除演算子
!
を使用できる。 - コンパイラはすべての null 分析を実行し、
null
を逆参照する可能性がある場合に警告を出す。
ディレクティブ | 参照型 | ?の使用 | 警告の出力 | ?を付けない型への nullの代入 | 制御フロー解析と 逆参照の警告 | !の効果 |
disable | すべてnull許容 | 警告 | × | ○ | × | × |
enable | ?なし:null非許容 ?あり:null許容 | ○ | ○ | 警告 | ○ | ○ |
disable annotations | すべてnull許容 | 警告 | × | ○ | × | × |
enable annotations | ?なし:null非許容 ?あり:null許容 | ○ | × | ○ | × | × |
disable warnings | すべてnull許容 | 警告 | × | ○ | × | × |
enable warnings | すべてnull許容※ | 警告 | ○ | 警告 | ○ | × |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
//プロジェクトのデフォルトがnullable disableのプロジェクト #nullable disable //コンパイラは、C# 7.3 以前と同様に動作 { Hello hello1 = null; //参照型にnullを代入できる hello1.SayHello(); //警告なし(実行時エラー) Hello? hello2 = null; //null許容参照型の使用→警告 hello2.SayHello(); //警告なし(実行時エラー) int? num = null; //null許容値型は使用可能 string str = null; //参照型にNullを代入できる Console.WriteLine(str.Length); //実行時エラー string? str2 = null;//null許容参照型の使用→警告 Console.WriteLine(str2.Length); //実行時エラー string? str3 = "あいうえお"; //null許容参照型の使用→警告 Console.WriteLine(str3.Length); string str4 = null; if (str4 != null) { Console.WriteLine(str4.Length); } Console.WriteLine(str4?.Length ?? 0); } #nullable restore //プロジェクトのデフォルトに戻す #nullable enable //null許容参照型のすべての機能が有効になる { Hello hello1 = null; //null非許容参照型にnullを代入→警告 hello1.SayHello(); //null参照の逆参照→警告(実行時エラー) Hello? hello2 = null; //null許容参照型の使用可能 hello2.SayHello(); //null参照の逆参照→警告(実行時エラー) int? num = null; string str = null; //null非許容参照型にnullを代入→警告 Console.WriteLine(str.Length); //null参照の逆参照→警告(実行時エラー) string? str2 = null; //null許容参照型の使用可能 Console.WriteLine(str2.Length); //null参照の逆参照→警告(実行時エラー) Console.WriteLine(str2!.Length); //null免除演算子で警告を消せる(本来はNullが入らないときにつける) string? str3 = "あいうえお"; Console.WriteLine(str3.Length); string str4 = null; //null非許容参照型にnullを代入→警告 if (str4 != null) { //nullでない場合しか到達できないので警告なし Console.WriteLine(str4.Length); } //nullかどうかを調べて対応するので警告なし Console.WriteLine(str4?.Length ?? 0); } #nullable restore #nullable enable annotations //null許容参照型の使用のみ可 { Hello hello1 = null; //null非許容参照型にnullを代入→警告はでない hello1.SayHello(); //null参照の逆参照→警告なし(実行時エラー) Hello? hello2 = null; //null許容参照型の使用可能 hello2.SayHello(); //null参照の逆参照→警告なし(実行時エラー) int? num = null; string str = null; //null非許容参照型にNullを代入→警告 Console.WriteLine(str.Length); //null参照の逆参照→警告なし(実行時エラー) string? str2 = null; //null許容参照型の使用可能 Console.WriteLine(str2.Length); //null参照の逆参照→警告なし(実行時エラー) Console.WriteLine(str2!.Length); //null免除演算子の効果なし string? str3 = "あいうえお"; Console.WriteLine(str3.Length); string str4 = null; if (str4 != null) { //nullでない場合しか到達できないので警告なし Console.WriteLine(str4.Length); } //nullかどうかを調べて対応するので警告なし Console.WriteLine(str4?.Length ?? 0); } #nullable restore #nullable enable warnings //警告のみ有効 { Hello hello1 = null; //参照型にnullを代入できる hello1.SayHello(); //null参照の逆参照→警告(実行時エラー) Hello? hello2 = null; //null許容参照型の使用→警告 hello2.SayHello(); //null参照の逆参照→警告(実行時エラー) int? num = null; string str = null; //参照型にnullを代入できる Console.WriteLine(str.Length); //null参照の逆参照→警告(実行時エラー) string? str2 = null; //null許容参照型の使用→警告 Console.WriteLine(str2.Length); //null参照の逆参照→警告(実行時エラー) Console.WriteLine(str2!.Length); //Null免除演算子で警告を消せる(本来はNullが入らないときにつける) string? str3 = "あいうえお"; //null許容参照型の使用→警告 Console.WriteLine(str3.Length); string str4 = null; if (str4 != null) { //nullでない場合しか到達できないので警告なし Console.WriteLine(str4.Length); } //nullかどうかを調べて対応するので警告なし Console.WriteLine(str4?.Length ?? 0); } #nullable restore |
#nullable enableとdisable annotations,disable warningsを併用することもできる。
#nullable enable コンテキスト中でdisable annotations,disable warningsを使った場合の挙動は以下。
参照型 | ?の使用 | 警告の出力 | ?を付けない型への nullの代入 | 制御フロー解析と 逆参照の警告 | !の効果 | |
disable annotations | すべてnull許容 | 警告 | ○ | ○ | ○ | ○ |
disable warnings | すべてnull許容 | ○ | × | ○ | × | × |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
#nullable enable #nullable disable annotations //null許容参照型の使用のみ無効 { Hello hello1 = null; //参照型にnullを代入できる hello1.SayHello(); //null参照の逆参照→警告(実行時エラー) Hello? hello2 = null; //null許容参照型の使用→警告 hello2.SayHello(); //null参照の逆参照→警告(実行時エラー) int? num = null; string str = null; //参照型にnullを代入できる Console.WriteLine(str.Length); ///null参照の逆参照→警告(実行時エラー) string? str2 = null; //null許容参照型の使用→警告 Console.WriteLine(str2.Length); //null参照の逆参照→警告(実行時エラー) Console.WriteLine(str2!.Length); //Null免除演算子で警告を消せる(本来はNullが入らないときにつける) string? str3 = "あいうえお"; Console.WriteLine(str3.Length); string str4 = null; if (str4 != null) { //nullでない場合しか到達できないので警告なし Console.WriteLine(str4.Length); } //nullかどうかを調べて対応するので警告なし Console.WriteLine(str4?.Length ?? 0); } #nullable enable #nullable disable warnings //警告のみ無効 { Hello hello1 = null; //null非許容参照型にnullを代入→警告なし hello1.SayHello(); //null参照の逆参照→警告なし(実行時エラー) Hello? hello2 = null; //null許容参照型の使用可能 hello2.SayHello(); //null参照の逆参照→警告なし(実行時エラー) int? num = null; string str = null; //null非許容参照型にNullを代入→警告なし Console.WriteLine(str.Length); //null参照の逆参照→警告なし(実行時エラー) string? str2 = null; //null許容参照型の使用可能 Console.WriteLine(str2.Length); //null参照の逆参照→警告なし(実行時エラー) Console.WriteLine(str2!.Length); //null免除演算子の効果なし(本来はNullが入らないときにつける) string? str3 = "あいうえお"; Console.WriteLine(str3.Length); string str4 = null; if (str4 != null) { //nullでない場合しか到達できないので警告なし Console.WriteLine(str4.Length); } //nullかどうかを調べて対応するので警告なし Console.WriteLine(str4?.Length ?? 0); } |
コメント