第3回 型による分岐の改良特集:C# 7の新機能詳説(2/2 ページ)

» 2017年08月02日 05時00分 公開
[山本康彦BluewaterSoft/Microsoft MVP for Windows Development]
前のページへ 1|2       

is演算子の拡張

 C# 7では、is演算子に3種類の拡張が施された。

  • 型判定と同時にキャスト(型パターン)
  • 定数とのマッチング(定数パターン)
  • 新しい変数に代入(varパターン)

型判定と同時にキャスト(型パターン)

 これは冒頭に挙げた「型判定してキャストする」問題を解決する機能だ。従来のis演算子の書き方の後ろに新しい変数を置くと、その型にキャストされて代入される(次のコード)。

object o = ……省略……
if (o is string s)
  WriteLine(s);

is演算子の型パターン(C# 7)
この例では変数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("それ以外の図形です");
}

is演算子の型パターンの使用例(C# 7)
冒頭のSampleMethod1をis演算子の型パターンを使って書き換えた。
型判定の後にas演算子を使ったキャストのコードが不要になり、すっきりした。

 なお、is演算子の型パターンは(後述する定数パターン/varパターンも)、条件式が使えるところならどこでも利用できる。例えば、条件演算子(三項演算子)の中で使う例を次のコードに示す。

object o1 = ……省略……
bool isCircle = (o1 is Ellipse e) ? (e.Width == e.Height) : false;

is演算子の型パターンを条件演算子の中で使う例(C# 7)
このコードでは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です");

is演算子の定数パターンの例(C# 7)
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日の金曜日です");

is演算子のvarパターンの例(C# 7)
例えば今日が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;
  }
}

switch文の新しい機能を使った例(C# 7)
元のコード(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;
}

型パターンを使うswitch文ではcaseラベルが上から順に評価される(C# 7)
必ず上から順に評価されるため、この例では下側の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;
}

型パターンのcaseラベルを並べると変数が未割り当てになる(C# 7)
最初の例では型パターンによってキャストされた変数eとrを使おうとしているが、実際に割り当てられるのは(変数oの型によって)eとrのどちらか一方だけである。他方は未割り当てのままとなるので、コンパイルエラーになってしまう。
2番目の例のように型パターンのwhen句の中だけで使うのならば、問題ない。

まとめ

 今回は、C# 7の新機能の中から、「パターンマッチング」と呼ばれているis演算子とswitch文の拡張を紹介した。その中で型パターンは、型による分岐と同時にキャストも行えるとても便利な機能なので、ぜひマスターして簡潔なコード記述に役立ててほしい。

「特集:C# 7の新機能詳説」のインデックス

特集:C# 7の新機能詳説

前のページへ 1|2       

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。