F# などの関数型言語をやっている方にはおなじみのパターンマッチング。「10 年おせーよ!」と言われそうですが、C# 7 にもそんな待ちに待った (?) 機能が搭載されそうです。主に既存の is
キーワードと switch
キーワードを拡張した、型に対するマッチングになる見込みです。本家ではこの機能を Type Switch と呼んでいるようです。パターンマッチングについては以下にまとめられています。
全体計画の一部を実装
パターンマッチングとしては他にも match
式や let
ステートメントなども検討されていますが、それらは C# 8 以降での追加になりそうです。詳細は以下を参照してください。
is 拡張
C# 6 までの is
キーワードは以下のように型判定に使われていました。
if (x is string) { //--- 変数 x が string 型だった場合の処理 }
このように is
の後ろに型を指定して判定するだけの機能でしたが、これが大幅に拡張されます。
定数マッチング
特定の値かどうかを判定できるようになります。等価演算子 (==) で良いのではないか?と思えるくらいには存在意義を見出せない...。
//--- 何か値があるとする var x = 123; //--- C# 6 まではこんな感じ var a = x == 123; //--- True var b = x == 456; //--- False var c = x == 'a'; //--- False //--- C# 7 ではこんな感じで判定できるように! var a = x is 123; //--- True var b = x is 456; //--- False var c = x is 'a'; //--- False var d = x is "123"; //--- Error!! : Cannot implicitly convert type 'string' to 'int'
型マッチング
特定の型かどうかの判定ができるようになります。...と言うと「それ今までと同じやん!」と思われるかもしれませんが、特定の型判定に加えて変数の生成を同時にやってくれます。以下のサンプルを見てください。
//--- 何か object 型に入った値があるとする object x = "abc"; //--- C# 6 ではこう var v = x as string; if (v != null) { //--- 変数 v に対する処理 } //--- もしくはこう if (x is string) { var v = (string)x; //--- 変数 v に対する処理 } //--- C# 7 からはこう書ける! if (x is string v) { //--- 変数 v に対する処理 }
見慣れない書き方に若干戸惑うかもしれませんが、以下のような動きをしています。
x
がstring
型かどうかを判定string
型だった場合、string
型の変数v
に値を代入- 評価結果を
bool
型で返す
型マッチングの変数スコープ
先の変数 v
のスコープは if 文のスコープのみです。なので、以下のように変数として評価結果を受ける場合は v
にアクセスできません。
object x = 123; var r = x is string v; Console.WriteLine(r); //--- false Console.WriteLine(v); //--- 変数 v を参照できないのでコンパイルエラー!
型マッチングが便利なシーン
以下のような null 許容型の判定などで便利そうです。だいぶスッキリと書ける印象があります。
//--- C# 6 ではこう int? v = x?.y?.z; if (v.HasValue) { var v = x.GetValueOrDefault(); //--- v を使った何か } //--- C# 7 ではこう書ける! //--- null のときの評価は当然 false if (x?.y?.z is int v) { //--- v を使った何か }
型マッチングの型判定は厳密
これまでの is
キーワードと同様、型マッチングの型判定は非常に厳密です。ですので暗黙的型変換などは期待してはいけません。
object x = 123; Console.WriteLine(x is int); //--- true Console.WriteLine(x is long); //--- false if (x is int v1){ /* OK */ } if (x is long v2){ /* NG */ } //--- 暗黙的型変換は当然してくれません
逆コンパイル
is 式の判定がどのように行われているか、ILSpy で逆コンパイルして覗いてみましょう。以下のように展開されます。
//--- これを逆コンパイルにかけると... static void Main() { object x = "abc"; if (x is string v1) Console.WriteLine(v1); if (x is int v2) Console.WriteLine(v2); } //--- こう展開される static void Main() { object x = "abc"; //--- 参照型の場合はそのまま as で判定 string v1 = x as string; bool flag = v1 != null; if (flag) Console.WriteLine(v1); //--- 値型の場合は null 許容型として判定 int? num = x as int?; int v2 = num.GetValueOrDefault(); //--- if 文の中で展開すればいいのでは?(と思ったり bool hasValue = num.HasValue; if (hasValue) Console.WriteLine(v2); }
参照型かどうかの判定の場合はただの as
キーワードで、値型かどうかの判定の場合は as
キーワード + null 許容型で行われるみたいですね。
switch 拡張
is
キーワードが拡張されたのと同様の機能が switch 文としても組み込まれます。書き方がちょっと変わっただけなので、is の書き方が分かれば難しくないですね!
object x = 123; switch (x) { case "abc": Console.WriteLine(x); break; case int v: Console.WriteLine(v); break; default: Console.WriteLine("default"); break; }
when 句による条件設定 (case guard)
C# 6 では 例外フィルター という catch
句の後ろに when
句を付ける条件分岐機能が追加されました。これと似た感じで、case
句の後ろに when
句を入れて条件分岐する「case guard」と呼ばれる機能が追加されます。以下のように使います。
object x = 123; switch (x) { case "abc": Console.WriteLine(x); break; case int v when 100 < v: //--- v が 100 より大きいとき Console.WriteLine(v); break; case int v: //--- v が 100 以下のとき Console.WriteLine(v); break; default: Console.WriteLine("default"); break; }
これでより細かい条件分岐を簡潔に書くことができるようになりましたね!
逆コンパイル
switch 文も逆コンパイルして展開結果を確認してみましょう。上のコードを展開すると以下のようになります。
object x = 123; object obj = x; //--- どーにも納得できないけど一度コピーが入ってる... if (object.Equals(obj, "abc")) { Console.WriteLine(x); } else { int? num = obj as int?; int v = num.GetValueOrDefault(); if (num.HasValue && 100 < v) //--- when 句で指定した条件も入っている { Console.WriteLine(v); } else { num = (obj as int?); int v2 = num.GetValueOrDefault(); //--- これも if の中で展開して欲しい if (num.HasValue) { Console.WriteLine(v2); } else { Console.WriteLine("default"); } } }
if 文を使って上手くやっている感じですね。