新しい構文がたくさん出てきてチュートリアルが進められない……。
情報源
例によって新しい構文の説明もなしに、知っている前提で話している感じ。私はそれらにつてい全く知らない。よって、まずは未知の構文を洗い出すことにした。ユースケースを理解するのはそれから。ドキュメントもそうして欲しい。理解すべき順序からしてそうだろJK。
コード例をGitHubから入手することになっているが、サイズが大きすぎるのでやめる。最小限のコードを自分で書いてみる。リポジトリを個別に分けてほしい。できれば一部ディレクトリのみ入手できるようになってほしい。
未知の構文
<LangVersion>8.0</LangVersion>
- C#のバージョンを指定する
- csprojファイルに追加することで
バージョンを指定する意味がわからない。おそらくnull安全に関する構文への対処を決定する要素のひとつなのだろう。警告メッセージなどがバージョンごとに変わるようになったときなどを想定しているのかも?
<Nullable>enable</Nullable>
も追加することで、はじめてnull許容型が使えるようになる、と思う。このとき変数宣言の末尾に?
がないものはnull非許容型となる。C#8.0において非許容型にnullが代入されたら、値型ならコンパイルエラー(これまで通り)、参照型ならコンパイル警告となる。
#nullable
ソースコードのうち、#nullable
ディレクティブ以降のコードは指定したnullableコンテキストになる。nullへの対応が決まる。たとえば#nullable enable
と書けば、以降の行でnull許容型を宣言できるようになる。
<Nullable>enable</Nullable>
にすると、そのプロジェクト全体がnull注釈コンテキストになる。既存プロジェクトの場合、それだと全箇所での修正が必要となり大変になってしまう。そこで一部だけnull許容型を使いたいときなどの場合、その箇所を#nullable
ディレクティブで囲う。
default
default
は各型に応じた既定値を返す。型ごとの既定値は規定値の一覧を参照。
int i = default(int); string s = default(string);
C#7.1以降は以下のようにdefaultリテラルが使える。
int i = default; string s = default;
defaultリテラルは他にも以下のような箇所で使える。
public void method1(int a = default) {} public int method2() { return default; } public void method3(int a) {} method3(default);
* !
演算子
要点は以下。
- null許容注釈コンテキスト内で使う
- 変数の末尾に
!
を付与することで使える - その値(式)が
null
でないことを宣言する - 目的は警告
CS8625
を回避すること(意図したnull
使用時)
public string Uri { get; set; } = default!;
使う場面は「意図したnull
使用時」である。たとえば以下2つ。
!
使用例1: 単体テスト
以下コードはnull
のとき例外発生することを期待する。まず#nullable
でnull許容コンテキストにする。このときstring
などの参照型は?
がないかぎりnull非許容型となる。以下コードは非許容型である。もしnull非許容型にnull
を代入するとコンパイル時に警告が出る。
実装コード
#nullable enable public class Person { public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name)); public string Name { get; } }
上記コードのうち例外発生をテストしたい。このときテストコード側でnull
リテラルを使う。先述の通り#nullable enable
のnull非許容型に対してnull
を渡すため警告が出る(Warning CS8625
)。ただ、実装コード側はそれでいいが、テストコード側は警告されたくない。意図してnull
を使うため警告は不要である。このとき、null!
のように末尾に!
をつけることで警告を回避できる。
テストコード
[TestMethod, ExpectedException(typeof(ArgumentNullException))] public void NullNameShouldThrowTest() { var person = new Person(null!); }
!
使用例2: 論理的に非null確定(コンパイラにはわからない)
public static void Main() { Person? p = Find("John"); if (IsValid(p)) { Console.WriteLine($"Found {p!.Name}"); } } public static bool IsValid(Person? person) { return person != null && !string.IsNullOrEmpty(person.Name); }
式の結果がnull
にならないことが明確だが、コンパイラがそれを認識できないときもある。このときコンパイラは警告を出す。この警告を回避したいとき!
が使える。
上記コードはnull
にならないことが明確なため、警告は無用の長物である。そこで警告を回避するため!
を付与している。p!
がそれである。
p
の値はPerson? p
のように宣言されており、null許容型である。型としてはnull
を代入できるが、if
文とその条件IsValid
メソッドによりnull
にはなり得ない。ただ、コンパイラはそれを理解できない。コンパイラにわかることは、Person?
のようにnull許容型として宣言されたことだけだ。つまりnull
になりうることだけわかっている。よって、$"{p.Name}"
とすると警告が出でしまう。
コンパイラがnullにならないことを理解できていないせいで起こる。回避したいとき!
を付与して$"{p!.Name}"
とする必要がある。
最初から?
をつけずnull非許容にすればいいのでは?
最初から?
をつけずnull非許容にすればいいのでは? と思う。だが、null許容かつ!
にすべき場合もある。たとえば途中まではnull
が入りうる場合があり、最終的には非nullであるとき。そのときは今回のようなnull許容型かつ!
を使うことになる。
null非許容型にできなかった理由がある。今回のコード例でいえばFind()
メソッドの仕様のせいだろう。Find("John")
は指定した名前John
が見つかればJohn
のPerson
インスタンスを返す。だが見つからなければnull
を返す仕様だろう。つまり、Find("John")
メソッド戻り値の型はnull許容型Person?
のはずだ。それを受け取る側の変数型も同じ型である必要がある。よってnull
を返す仕様であることから、null許容型にせざるを得ない。
もしFind()
メソッド内でPerson
を見つけられなかったとき、例外を発生させる仕様なら、null非許容型でよかったし、!
も不要だったろう。もっとも、そのときはtry〜cahtch
構文で握りつぶすか、プログラム中断することになるだろうが。
例外発生はパフォーマンス悪化に繋がるし、対応コードも長くなる。よって、#nullable enable
, ?
, !
, null
を使ったコーディングのほうが良いと判断しうる。null
を完全排除できない理由はいくつかあると思うが、パフォーマンスを気にする限りこれからもnull
と付き合っていかねばならないと思われる。
型におけるnull
値の許容パターン
null | 概要 |
---|---|
非許容 | null 割当不可 |
許容 | null 割当可。nullチェックなしに逆参照すると警告。 |
無関係 | C#8.0以前の状態。警告なし。 |
不明 | 指示なし状態。 |
ここには以下のように書いてあったが本当か?
- "null 非許容" : この型の変数には、null を割り当てることはできません。 この型の変数については、逆参照する前に null チェックを行う必要はありません。
つまり、以下は不可だと? 警告が出るだけでは?
#nullable enable string s = null;
nullコンテキスト
コンテキスト設定
設定方法 | コンテキスト | null許容 | |
---|---|---|---|
<Nullable> | 注釈 | 警告 | 参照型 |
enable | ○ | ○ | 非 |
warnings | ☓ | ○ | 無 |
annotations | ○ | ☓ | 非 |
disable | ☓ | ☓ | 無 |
設定方法 | コンテキスト | |
---|---|---|
#nullable | 注釈 | 警告 |
enable | ○ | ○ |
disable | ☓ | ☓ |
restore | pj | pj |
enable warnings | − | ○ |
disable warnings | − | ☓ |
restore warnings | − | pj |
enable annotations | ○ | − |
disable annotations | ☓ | − |
restore annotations | pj | − |
公式によるとrestore
は「プロジェクト設定に戻す」らしいが、ここでは「1つ前の設定に戻す」とある。どっちだよ。
以下、設定例。
.csproj
<Nullable>enable<Nullable>
.cs
#nullable enable ... #nullable disable
所感
理解するだけで大変そう。以下のようなことを把握したいのだが、そんな資料があるのかわからん。
せめてnull安全に関するC#8.0構文だけをまとめた文書とかないの?
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13 ※
- bash 4.4.12(1)-release ※
- SQLite 3.29.0 ※
- C# dotnet 3.0.100
$ uname -a Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux