C# 7では、is演算子に3種類の拡張が施された。
これは冒頭に挙げた「型判定してキャストする」問題を解決する機能だ。従来のis演算子の書き方の後ろに新しい変数を置くと、その型にキャストされて代入される(次のコード)。
object o = ……省略……
if (o is string s)
  WriteLine(s);
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("それ以外の図形です");
}
なお、is演算子の型パターンは(後述する定数パターン/varパターンも)、条件式が使えるところならどこでも利用できる。例えば、条件演算子(三項演算子)の中で使う例を次のコードに示す。
object o1 = ……省略……
bool isCircle = (o1 is Ellipse e) ? (e.Width == e.Height) : false;
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です");
is演算子のvarパターンは、もはや何の比較も行わず、常にtrueを返す。ただし、varで宣言した新しい変数に代入される。
これは何に使うのだろうと思ってしまうかもしれないが、例えば次のコードのように、条件式の中でメソッドなどを呼び出したときに、その返値を条件式の中で繰り返し使えるのである。
if (DateTime.Today is var today
    && today.Day is 13 
    && today.DayOfWeek is DayOfWeek.Friday)
  WriteLine("今日は13日の金曜日です");
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;
  }
}
この型パターンを使う新しい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ラベルを並べて、それらを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;
}
今回は、C# 7の新機能の中から、「パターンマッチング」と呼ばれているis演算子とswitch文の拡張を紹介した。その中で型パターンは、型による分岐と同時にキャストも行えるとても便利な機能なので、ぜひマスターして簡潔なコード記述に役立ててほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.