ホームに戻る
出典 :
関連 :
目次 :
パターンマッチングとは
入力式が特定のパターン(型、値、要素数など)にマッチしているかを検査することができる構文。C#7.0で導入された。
switch 式で用いるのが強力である。
尚、「正規表現(パターンマッチング)」とは無関係である。
宣言パターン・型パターン
「宣言パターン」では入力式が指定された型と互換性があるかをチェックすることができる。
新しいローカル変数を宣言でき、宣言パターンが入力式に合致すると、変換された式の結果がその変数に代入される。

型チェックのみで変数への代入が不要な場合は変数名の代わりに _ (アンダースコア)を使用できるが、C#9.0以降は _ を省略できる(「型パターン」)。
// 抽象クラス Vehicle と Vehicle を継承する Car , Truck
public abstract class Vehicle {}
public class Car : Vehicle {}
public class Truck : Vehicle {}
public static class TollCalculator
{
// 宣言パターンによる比較
public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{
Car _ => 2.00m, //< vehicle is Car _
Truck _ => 7.50m, //< vehicle is Truck _
null => throw new ArgumentNullException(nameof(vehicle)), //< vehicle is null
_ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)), //< それ以外 (破棄パターン)
};
// 型パターンによる比較
public static decimal CalculateToll_New(this Vehicle vehicle) => vehicle switch
{
Car => 2.00m, //< vehicle is Car
Truck => 7.50m, //< vehicle is Truck
null => throw new ArgumentNullException(nameof(vehicle)), //< vehicle is null
_ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)), //< それ以外 (破棄パターン)
};
}
定数パターン
入力式の結果が指定された定数と等しいかどうかをチェックできる。
public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
1 => 12.0m, //< visitorCount == 1
2 => 20.0m, //< visitorCount == 2
3 => 27.0m, //< visitorCount == 3
4 => 32.0m, //< visitorCount == 4
0 => 0.0m, //< visitorCount == 0
_ => throw new Exception("適用できません"), //< それ以外 (破棄パターン)
};
定数パターンでは、以下の定数式を使用できる。
- 数値リテラル(整数または浮動小数点数)
- char
- 文字列リテラル
- ブール値( true または false )
- 列挙型値
- 宣言された定数フィールドまたはローカルの名前
- null
リレーショナルパターン
関係演算子( < 、> 、<= 、>= )と定数式の組み合わせで表現される条件に合致しているかをチェックできる。C#9.0以降で使用可能。
static string Classify(double measurement) => measurement switch
{
< -4.0 => "Too low", //< リレーショナルパターン : measurement < -4.0
> 10.0 => "Too high", //< リレーショナルパターン : measurement > 10.0
double.NaN => "Unknown", //< 定数パターン : measurement == double.NaN
_ => "Acceptable", //< 破棄パターン : 上記以外
};
論理パターン
パターン連結子( not 、and 、or )を使用して、条件を結合・否定できる。C#9.0以降で使用可能。
下記の例では、複数のパターンを and によって連結している。
static string Classify(double measurement) => measurement switch
{
< -40.0 => "Too low", //< measurement < -40.0
>= -40.0 and < 0 => "Low", //< -40.0 <= measurement < 0
>= 0 and < 10.0 => "Acceptable", //< 0 <= measurement < 10.0
>= 10.0 and < 20.0 => "High", //< 10.0 <= measurement < 20.0
>= 20.0 => "High", //< 20.0 <= measurement
double.NaN => "Unknown",
};
連結子の優先順位は not > and > or で、優先順位を明示するために括弧を使用できる。
// input が float または double のいずれかでなければ抜ける
if (input is not (float or double))
{
return;
}
プロパティパターン
入力式(オブジェクト)のプロパティまたはフィールドをチェックできる。

また、宣言・型パターンと組み合わせて型チェック、変数宣言を同時に行うことも可能。
Console.WriteLine(TakeFive("Hello, world!")); // output: Hello
Console.WriteLine(TakeFive("Hi!")); // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' })); // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' })); // output: abc
// 先頭から 5 要素を摘出する
static string TakeFive(object input) => input switch
{
string { Length: >= 5 } s => s.Substring(0, 5), //< input が string 、かつ Length >= 5 ならば 先頭 5 文字を摘出
string s => s, //< input が string 、かつ上記以外ならばそのまま返す
ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()), //< input が ICollection<char> 、かつ Count >= 5 ならば 先頭 5 要素を摘出し、文字列に変換
ICollection<char> symbols => new string(symbols.ToArray()), //< input が ICollection<char> 、かつ上記以外ならばそのまま文字列に変換
null => throw new ArgumentNullException(nameof(input)),
_ => throw new ArgumentException("Not supported input type."),
};
プロパティパターンは入れ子(ネスト)が可能で、内側の要素を参照できる。
C#10.0以降は「拡張プロパティパターン」として、. (ドット)によるメンバ参照が可能である。
public record Point(int X, int Y);
public record Segment(Point Start, Point End);
// segment.Start.Y == 0 または segment.End.Y == 0 ならば true
static bool IsAnyEndOnXAxis(Segment segment) =>
segment is { Start: { Y: 0 } } or { End: { Y: 0 } };
// segment.Start.Y == 0 または segment.End.Y == 0 ならば true ( IsAnyEndOnXAxis(Segment segment) と等価)
static bool IsAnyEndOnXAxis_New(Segment segment) =>
segment is { Start.Y: 0 } or { End.Y: 0 };
位置指定パターン
入力式にタプル( ValueTuple )をとり、タプルの各要素に対してパターンを適用できる。
static decimal GetGroupTicketPriceDiscount( int groupSize, DateTime visitDate )
=> ( groupSize, visitDate.DayOfWeek ) switch
{
( <= 0 , _ ) => throw new Exception("Invalid group size."), //< groupSize <= 0 ( visitDate.DayOfWeek 不問)
( _ , DayOfWeek.Saturday or DayOfWeek.Sunday ) => 0.0m, //< ( groupSize 不問) visitDate.DayOfWeek == DayOfWeek.Saturday or
visitDate.DayOfWeek == DayOfWeek.Sunday
( >= 5 and < 10 , DayOfWeek.Monday ) => 20.0m, //< 5 <= groupSize < 10 && visitDate.DayOfWeek == DayOfWeek.Monday
( >= 10 , DayOfWeek.Monday ) => 30.0m, //< 10 <= groupSize && visitDate.DayOfWeek == DayOfWeek.Monday
( >= 5 and < 10 , _ ) => 12.0m, //< 5 <= groupSize < 10 ( visitDate.DayOfWeek 不問)
( >= 10 , _ ) => 15.0m, //< 10 <= groupSize ( visitDate.DayOfWeek 不問)
_ => 0.0m, //< 上記以外(破棄パターン)
};
プロパティパターン同様、位置指定パターンも入れ子にすることができる。
var パターン
var パターンは null を含む任意の式に一致し、その結果を新しいローカル変数に代入する。
// var パターン使用 ⇒ result = SimulateDataFetch(id) となる
static bool IsAcceptable(int id, int absLimit) =>
SimulateDataFetch(id) is var results
&& results.Min() >= -absLimit
&& results.Max() <= absLimit;
static int[] SimulateDataFetch(int id)
{
var rand = new Random();
return Enumerable
.Range(start: 0, count: 5)
.Select(s => rand.Next(minValue: -10, maxValue: 11))
.ToArray();
}
ブール式内に中間結果を保持するための一時変数を作成する場合に有用である。
リストパターン
C#11.0以降で使用可能。シーケンス(配列または List<T> )をパターンのシーケンスと照合する。
入力シーケンスの全要素ではなく、先頭、または末尾のみを照合する場合は .. (スライスパターン)を使用する。
int[] numbers = { 1, 2, 3 };
// 全要素を照合
Console.WriteLine(numbers is [1, 2, 3]); // True
Console.WriteLine(numbers is [1, 2, 4]); // False
Console.WriteLine(numbers is [1, 2, 3, 4]); // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]); // True
// 先頭を照合
Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]); // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]); // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]); // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]); // False
// 末尾を照合
Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]); // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]); // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]); // True
// 先頭および末尾を照合
Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]); // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]); // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]); // False
また、スライスパターン中にサブパターンを入れ子にすることもできる。
void MatchMessage(string message)
{
// スライスパターン中に var パターンを挿入
var result = message is ['a' or 'A', .. var s, 'a' or 'A']
? $"Message {message} matches; inner part is {s}."
: $"Message {message} doesn't match.";
Console.WriteLine(result);
}
MatchMessage("aBBA"); // output: Message aBBA matches; inner part is BB.
MatchMessage("apron"); // output: Message apron doesn't match.
void Validate(int[] numbers)
{
// スライスパターン中にプロパティパターンを挿入
var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid";
Console.WriteLine(result);
}
Validate(new[] { -1, 0, 1 }); // output: not valid
Validate(new[] { -1, 0, 0, 1 }); // output: valid