F# などの関数型言語をやっている方にはおなじみのパターンマッチング。「10 年おせーよ!」と言われそうですが、C# 7 にもそんな待ちに待った (?) 機能が搭載されそうです。主に既存の is
キーワードと switch
キーワードを拡張した、型に対するマッチングになる見込みです。本家ではこの機能を Type Switch と呼んでいるようです。パターンマッチングについては以下にまとめられています。
全体計画の一部を実装
パターンマッチングとしては他にも match
式や let
ステートメントなども検討されていますが、それらは C# 8 以降での追加になりそうです。詳細は以下を参照してください。
is 拡張
C# 6 までの is
キーワードは以下のように型判定に使われていました。
if (x is string)
{
}
このように is
の後ろに型を指定して判定するだけの機能でしたが、これが大幅に拡張されます。
定数マッチング
特定の値かどうかを判定できるようになります。等価演算子 (==) で良いのではないか?と思えるくらいには存在意義を見出せない...。
var x = 123;
var a = x == 123;
var b = x == 456;
var c = x == 'a';
var a = x is 123;
var b = x is 456;
var c = x is 'a';
var d = x is "123";
型マッチング
特定の型かどうかの判定ができるようになります。...と言うと「それ今までと同じやん!」と思われるかもしれませんが、特定の型判定に加えて変数の生成を同時にやってくれます。以下のサンプルを見てください。
object x = "abc";
var v = x as string;
if (v != null)
{
}
if (x is string)
{
var v = (string)x;
}
if (x is string v)
{
}
見慣れない書き方に若干戸惑うかもしれませんが、以下のような動きをしています。
x
が string
型かどうかを判定
string
型だった場合、string
型の変数 v
に値を代入
- 評価結果を
bool
型で返す
型マッチングの変数スコープ
先の変数 v
のスコープは if 文のスコープのみです。なので、以下のように変数として評価結果を受ける場合は v
にアクセスできません。
object x = 123;
var r = x is string v;
Console.WriteLine(r);
Console.WriteLine(v);
型マッチングが便利なシーン
以下のような null 許容型の判定などで便利そうです。だいぶスッキリと書ける印象があります。
int? v = x?.y?.z;
if (v.HasValue)
{
var v = x.GetValueOrDefault();
}
if (x?.y?.z is int v)
{
}
型マッチングの型判定は厳密
これまでの is
キーワードと同様、型マッチングの型判定は非常に厳密です。ですので暗黙的型変換などは期待してはいけません。
object x = 123;
Console.WriteLine(x is int);
Console.WriteLine(x is long);
if (x is int v1){ }
if (x is long v2){ }
逆コンパイル
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";
string v1 = x as string;
bool flag = v1 != null;
if (flag)
Console.WriteLine(v1);
int? num = x as int?;
int v2 = num.GetValueOrDefault();
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:
Console.WriteLine(v);
break;
case int v:
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)
{
Console.WriteLine(v);
}
else
{
num = (obj as int?);
int v2 = num.GetValueOrDefault();
if (num.HasValue)
{
Console.WriteLine(v2);
}
else
{
Console.WriteLine("default");
}
}
}
if 文を使って上手くやっている感じですね。