第3回 型による分岐の改良:特集:C# 7の新機能詳説(1/2 ページ)
変数の型によって処理を分岐したり、その際にキャストをしたりするのは、多くのプログラミング言語でよく行われることだ。C# 7ではこれをとても簡潔に記述できる。
Visual Studio 2017とともにリリースされたC# 7には多くの新機能がある。それらの新機能はどのような場面で役立つのだろうか? 3回にわたって紹介していく。
- 第1回:明瞭なコーディングのために
- 第2回:簡潔なコーディングのために
- 第3回:型による分岐の改良(本稿)
型による分岐の改良
型によって分岐するコードは煩雑になりがちだ。今回は、それをきれいなコードにするのに役立つC# 7の新機能「パターンマッチング」を紹介する。
- is演算子の拡張
- switch文の拡張
型による分岐の課題(従来のコード)
型によって分岐するコードとは、例えば次のコードのような場合だ(C# 6で記述)。
// 次への参照追加が必要:PresentationFramework、PresentationCore、WindowsBase
using System;
using System.Windows.Shapes;
using static System.Console;
class Program
{
public static void SampleMethod1(Shape s)
{
if (s is Ellipse) // 型を判定しても……
{
var e = s as Ellipse; // その型として使うにはキャストが必要
if (e.Width == e.Height)
WriteLine($"直径が{e.Width}の円です");
else
WriteLine("だ円です");
}
else if (s is Rectangle) // else ifが連続する
WriteLine("長方形です");
else if (s == null)
WriteLine("nullです");
else
WriteLine("それ以外の図形です");
}
[STAThread]
static void Main(string[] args)
{
SampleMethod1(new Ellipse { Width = 1.0, Height = 1.0, });
// 出力:直径が1の円です
SampleMethod1(new Ellipse { Width = 1.0, Height = 2.0, });
// 出力:だ円です
SampleMethod1(new Rectangle());
// 出力:長方形です
SampleMethod1(new Line());
// 出力:それ以外の図形です
SampleMethod1(null);
// 出力:nullです
#if DEBUG
ReadKey();
#endif
}
}
コンソールアプリの例である。WPFのオブジェクトを使っている。
SampleMethod1メソッドは、引数に渡された図形によって、コンソールに円・だ円・長方形・それ以外と出力するものだ。その内部では、is演算子による型の判定と、as演算子によるキャストが行われている。この例ではキャストを1回しか行っていないが、実際のコーディングではもっとたくさんキャストを行うだろう。
上のコードのSampleMethod1メソッドでは、is演算子で型を判定し、直後にas演算子でキャストしている。ここではサンプルということでキャストは1回しか登場していないが、実際にはもっと多いだろう。
このis演算子とas演算子の連続を避けるため、C# 6までは次のようなコードを書くこともあった。
Ellipse e;
if ((e = s as Ellipse) != null)
{
if (e.Width == e.Height)
WriteLine($"直径が{e.Width}の円です");
else
WriteLine("だ円です");
}
is演算子を使わずにいきなりas演算子でキャストすると、型が合わないときは結果がnullになる。そのnull判定だけで、型判定とキャストがいっぺんにできていることになるわけだ。
ただし、事前にローカル変数を宣言しておかなければならない。if〜else if〜が連続する場合は、いくつものローカル変数宣言を冒頭に書いておかねばならないのだ。
また、先のコードのSampleMethod1メソッドは、if〜else if〜が連続していて読みにくい。C# 6までは、次のコードのような工夫を凝らしてswitch文を使うこともあった。
public static void SampleMethod2(Shape s)
{
switch (s?.GetType().Name) // 文字列にすればswitchできる
{
case nameof(Ellipse):
var e = s as Ellipse; // やはり分岐後にキャストが必要
if (e.Width == e.Height) // 円とだ円の判定にはif文が必要
WriteLine($"直径が{e.Width}の円です");
else
WriteLine("だ円です");
break;
case nameof(Rectangle):
WriteLine("長方形です");
break;
case null:
WriteLine("nullです");
break;
default:
WriteLine("それ以外の図形です");
break;
}
}
C# 6以前は、caseラベルに文字列リテラルを書くしかなかった。C# 6からはnameof演算子が使える。また、C# 6から使えるようになったnull条件演算子によって、事前のnullチェックを不要にしている。
それでも、その都度キャストが必要なのは先のコードと変わらない(いきなりas演算子でキャストする手法はcaseラベルでは使えない)。また、円とだ円の判定でやっているようにif文を全て排除できるとも限らない。
このようにswitch文にすると少しは読みやすいだろう。それでも、いちいちキャストしなければならないことには変わりないし、全ての分岐をcaseラベルで書けないことも多い。
上の2つのコードに共通するのは、「型判定してキャストする」の繰り返しが多く登場するということだ(サンプルなのでキャストは1回にとどめているが、実際には頻繁に現れるはずだ)。
問題点を把握できたところで、C# 7の新機能を解説していこう。型判定してキャストするコードをシンプルに書けるし、型をそのまま使ってswitchもできるのだ。さらにそれ以外の機能もある。これらの新機能は、まとめて「パターンマッチング」と呼ばれている。
Copyright© Digital Advantage Corp. All Rights Reserved.