第3回 型による分岐の改良:特集:C# 7の新機能詳説(2/2 ページ)
変数の型によって処理を分岐したり、その際にキャストをしたりするのは、多くのプログラミング言語でよく行われることだ。C# 7ではこれをとても簡潔に記述できる。
is演算子の拡張
C# 7では、is演算子に3種類の拡張が施された。
- 型判定と同時にキャスト(型パターン)
- 定数とのマッチング(定数パターン)
- 新しい変数に代入(varパターン)
型判定と同時にキャスト(型パターン)
これは冒頭に挙げた「型判定してキャストする」問題を解決する機能だ。従来のis演算子の書き方の後ろに新しい変数を置くと、その型にキャストされて代入される(次のコード)。
object o = ……省略……
if (o is string s)
WriteLine(s);
この例では変数oがstring型のとき、従来のis演算子と同様にtrueを返すと同時に、oがstring型にキャストされて変数sに代入される。
この変数sは、この場所で宣言されるものだ。事前に宣言しておいた変数は使えない。
is演算子がtrueになったときだけif文の中が実行されるので、if文の中では変数sはnullではないことが保証される。
変数sのスコープは、if文とそれ以降である。if文の中だけではないので注意してほしい。
なお、ここではif文で使っているが、条件式を使えるところならどこでも型パターンを使える(条件演算子の例を後述する)。
is演算子の型パターンを使って冒頭のSampleMethod1を書き直すと、次のコードのようになる。
public static void SampleMethod3(Shape s)
{
if (s is Ellipse e) // 型と判定と同時にキャストしてeに代入
{
if (e.Width == e.Height)
WriteLine($"直径が{e.Width}の円です");
else
WriteLine("だ円です");
}
else if (s is Rectangle)
WriteLine("長方形です");
else if (s == null)
WriteLine("nullです");
else
WriteLine("それ以外の図形です");
}
冒頭のSampleMethod1をis演算子の型パターンを使って書き換えた。
型判定の後にas演算子を使ったキャストのコードが不要になり、すっきりした。
なお、is演算子の型パターンは(後述する定数パターン/varパターンも)、条件式が使えるところならどこでも利用できる。例えば、条件演算子(三項演算子)の中で使う例を次のコードに示す。
object o1 = ……省略……
bool isCircle = (o1 is Ellipse e) ? (e.Width == e.Height) : false;
このコードではo1がEllipse型の場合、Ellipse型にキャストされて新しい変数eに代入され、条件演算子の2番目の部分で円かどうかの判定がされて、その結果がisCircle変数に代入される。
o1がEllipse型ではない場合は、条件演算子の3番目の部分のfalseがisCircle変数に代入される。
なお、この例は条件演算子を使わずにAND条件としても書けるが、サンプルということでご容赦願いたい。
定数とのマッチング(定数パターン)
is演算子で定数との比較も可能になった(次のコード)。
object o1 = 123;
// 従来の書き方
if (object.Equals(o1, 123))
WriteLine("o1は123です");
// is演算子(定数パターン)
if (o1 is 123)
WriteLine("o1は123です");
object o2 = null;
// 従来の書き方
if (o2 == null)
WriteLine("o2はnullです");
// is演算子(定数パターン)
if (o2 is null)
WriteLine("o2はnullです");
object.Equalsを使うよりis演算子の方がすっきりする。
「is null」という書き方はVisual Basicのようだ。
新しい変数に代入(varパターン)
is演算子のvarパターンは、もはや何の比較も行わず、常にtrueを返す。ただし、varで宣言した新しい変数に代入される。
これは何に使うのだろうと思ってしまうかもしれないが、例えば次のコードのように、条件式の中でメソッドなどを呼び出したときに、その返値を条件式の中で繰り返し使えるのである。
if (DateTime.Today is var today
&& today.Day is 13
&& today.DayOfWeek is DayOfWeek.Friday)
WriteLine("今日は13日の金曜日です");
例えば今日が13日かどうかを判定するだけなら、「DateTime.Today.Day == 13」だけで済む。ところが、さらに金曜日かどうかも調べたいときは、従来ならば事前にDateTime.Todayの値をローカル変数に格納しておかなければならなかった。varパターンを使えば、この例のように条件式の中だけで完結する。
varパターンの新しい変数(この例では「today」)の型は、is演算子の左側の式(この例では「DateTime.Today」)から推論される(この例ではDateTime型になる)。そのため、条件式の後ろの部分でDateTime構造体のメソッドを呼び出せている。
なお、この例では定数パターンも使っている。
switch文の拡張
switch文に与える式に制限がなくなり、caseラベルで(is演算子で説明した)型パターンが利用できるようになった。さらに、caseラベルの後ろにwhen句を置ける。
冒頭のSampleMethod2メソッドは、C# 7では次のコードのように書ける。
public static void SampleMethod4(Shape s)
{
switch (s) // 参照型でもOK
{
case Ellipse e when e.Width == e.Height:
// sがEllipseであり、かつ、when句の条件を満たす場合に、
// sはEllipse型の変数eにキャストされて、この分岐に来る
WriteLine($"直径が{e.Width}の円です");
break;
case Ellipse e: // 上のwhen句でマッチしなかったEllipseは、こちらに来る
WriteLine("だ円です");
break;
case Rectangle r: // 変数rの省略は不可
WriteLine("長方形です");
break;
case null:
WriteLine("nullです");
break;
default:
WriteLine("それ以外の図形です");
break;
}
}
元のコード(SampleMethod2メソッド)にあった分岐後のキャストがなくなっている。また、元のコードでは円とだ円を区別するためにif文を使っていたが、このコードではwhen句を使って全ての分岐をcaseラベルで指定できている。
なお、新しい変数のスコープは、(if文のときとは違って)そのcaseラベルに続くswitchセクションの中だけである。
新しいcaseラベルは上から順に評価される
この型パターンを使う新しいcaseラベルは、上から順に評価される(defaultを除く)。記述順序を間違えると評価されないcaseラベルが出てくることもあるので注意してほしい(次のコード)。
switch (s)
{
default:
WriteLine("それ以外の図形です");
break;
case Ellipse e:
// sがEllipse型であれば、必ずここに入ってしまう
WriteLine("だ円です");
break;
case Ellipse e when e.Width == e.Height:
// sがEllipse型のときは全て上のcaseに入ってしまい、ここには絶対に来ない
WriteLine($"直径が{e.Width}の円です");
break;
}
必ず上から順に評価されるため、この例では下側のcaseラベルは実行されない。
例外はdefaultで、どこに書いてあっても最後に評価される。
なお、型パターンを含まない従来のswitch文は、コンパイル時の最適化により上から順に評価されるとは限らない(効率の良いジャンプテーブルとしてコンパイルされる場合もある)。
複数のcaseラベルを並べるとき
複数のcaseラベルを並べて、それらを1つのswitchセクションに割り当てることがある。型パターンを使う新しい書き方でもそれは可能なのだが、新しい変数が未割り当てになってしまうので注意が必要だ(次のコード)。
object o = ……省略……
switch(o)
{
// これはコンパイルエラーになる
//case Ellipse e:
//case Rectangle r:
// ここに来たとき、変数eと変数rのどちらかは未割り当て
// if (e.Width < 1.0 || r.Width < 1.0)
// WriteLine("幅が狭い図形");
// break;
// これはOK
case Ellipse e when e.Width < 1.0:
case Rectangle r when r.Width < 1.0:
WriteLine("幅が狭い図形");
break;
case null:
WriteLine("nullです");
break;
default:
WriteLine("それ以外のオブジェクトです");
break;
}
最初の例では型パターンによってキャストされた変数eとrを使おうとしているが、実際に割り当てられるのは(変数oの型によって)eとrのどちらか一方だけである。他方は未割り当てのままとなるので、コンパイルエラーになってしまう。
2番目の例のように型パターンのwhen句の中だけで使うのならば、問題ない。
まとめ
今回は、C# 7の新機能の中から、「パターンマッチング」と呼ばれているis演算子とswitch文の拡張を紹介した。その中で型パターンは、型による分岐と同時にキャストも行えるとても便利な機能なので、ぜひマスターして簡潔なコード記述に役立ててほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.