できるだけ再帰パターンである位置、プロパティの2パターンを使えばいい感じ。
成果物
背景
C#8.0のパターンマッチはできることが多い。どういうとき、どう書くのがベストなのかわからない。大まかな方針だけでも掴みたいので考えてみた。
一覧
| ベストプラクティス | 理由 |
|---|---|
switch式を使うべし(switch文でなく) |
case, break, defaultなどを省略できる。 |
| 型は1つに絞るべし | 名前を省略できる(位置・プロパティパターンで) |
プロパティパターンを使うべし(when句でなく) |
条件の重複チェックしてくれる |
switch式において、マッチした値のメンバを参照するか否かによって最適なパターンが変わる。
| メンバ参照 | ベストプラクティス | 理由 |
|---|---|---|
| する | プロパティパターンの非nullマッチを使うべし(varパターンでなく) | varはnullにもマッチするから(NullReferenceException例外発生しうる) |
| しない | 破棄パターンを使うべし | _で参照不可になるから |
1. switch式を使うべし(switch文でなく)
switch文
switch (x) { case 0: Console.WriteLine("Case 0"); break; case 1: Console.WriteLine("Case 1"); break; default: Console.WriteLine("Case default"); break; }
switch式
string M(object x) => x switch { 0 => "Case 0", 1 => "Case 1", _ => "Case default", } Console.WriteLine(M(x));
圧倒的スマート!
2. 型は1つに絞るべし
絞れるときは絞ったほうがいい。型の名前を省略できるから。
before
int Bad(object x) x switch => { Point(1, 2) => 0, _ => -1, };
after
int Best(Point p) p switch => { (1, 2) => 0, _ => -1, };
3. プロパティパターンを使うべし(when句でなく)
条件が重複しているとき、コンパイルエラーで通知してくれるから。
when句
int M1(object obj) => obj switch { string s when s.Length == 0 => 0, string s when s.Length == 0 => 1, // 条件重複! _ => -1, };
プロパティパターン
int M2(object obj) => obj switch { string { Length: 0 } => 0, string { Length: 0 } => 1, // 条件重複! コンパイルエラーで通知してくれる _ => -1, };
error CS8510: このパターンは、switch 式の前の arm で既に処理されています。
コピペしたまま修正忘れたときとか、こうなりそう。もしエラーにしてくれたらコンパイルの時点でミスに気付ける。だが、エラーにしてくれなければ、単体テストで失敗するまで気付けない。
4. マッチ値のメンバ参照
switch式において、マッチした値のメンバを参照するか否かによって最適なパターンが変わる。
| メンバ参照 | ベストプラクティス | 理由 |
|---|---|---|
| する | プロパティパターンの非nullマッチを使うべし(varパターンでなく) | varはnullにもマッチするから(NullReferenceException例外発生しうる) |
| しない | 破棄パターンを使うべし | _で参照不可になるから |
4-1. マッチ値のメンバを参照しないなら、破棄パターンを使うべし
varパターン
string M(object x) => x switch { 0 => "Case 0", 1 => "Case 1", var other => "Case default", } Console.WriteLine(M(x));
破棄パターン
string M(object x) => x switch { 0 => "Case 0", 1 => "Case 1", _ => "Case default", } Console.WriteLine(M(x));
破棄パターンのほうがスマート。
だが、メンバ参照はできない。以下のようなエラーとなる。
string M(object x) => x switch { 0 => "Case 0", 1 => "Case 1", _ => _.ToString(), }; Console.WriteLine(M(2));
error CS0103: 現在のコンテキストに '_' という名前は存在しません。
4-2. マッチ値のメンバを参照するなら、プロパティパターンを使うべし(varパターンでなく)
メンバ参照するときは以下の条件が必須である。
- 変数が必要
- 非nullであること
プロパティパターン
string Best(Point p) => p switch { { } nonNull => nonNull.ToString(), null => "null", }; Console.WriteLine(Best(new Point(1,2)));
varパターン
string Bad(Point p) => p switch { null => "null", var other => other.ToString(), }; Console.WriteLine(Bad(new Point(1,2)));
上記は結果的に同じ。ただし、varパターンのvar otherにはnullも入りうる。nullパターンチェックと重複してしまう。なので、もし間違ってnullパターンを後ろに書いてしまうと、条件重複コンパイルエラーになる。
string Bad2(Point p) => p switch { var other => other.ToString(), null => "null", }; Console.WriteLine(Bad2(null));
error CS8510: このパターンは、switch 式の前の arm で既に処理されています。
NullReferenceExceptionが発生しるう事態にはならないので、そこは安心。だが、順序が変わっただけでコンパイルエラーになるのはウザい。将来コードをメンテナンスするとき面倒。なのでvarパターンよりプロパティパターンのほうが良い。
つまり_がダメなら{ }。varはいらない子。それぞれ文字数が1, 2, 3。少ない順に優先して使うものと覚えればいいかも?
対象環境
- 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