C# 9 以降:パターンマッチング徹底解説 – 進化、活用、パフォーマンス
C# は、その誕生以来、開発者の生産性を高め、安全なコードを書くための機能を追加し続けてきました。特に、C# 9 以降、パターンマッチングは大きく進化し、より強力で柔軟なツールへと変貌を遂げました。本記事では、C# のパターンマッチングに焦点を当て、その進化の過程、具体的な活用方法、パフォーマンスへの影響までを詳細に解説します。
1. パターンマッチングとは?
パターンマッチングは、データの形状(型、プロパティ、値など)に基づいてコードの実行経路を決定するプログラミング技術です。従来の if-else
や switch
文よりも簡潔かつ表現力豊かに条件分岐を記述できるため、コードの可読性、保守性を向上させることができます。
2. C# におけるパターンマッチングの歴史と進化
C# 7 で導入されたパターンマッチングは、C# 9 以降、以下の点で大きく進化しました。
- 型のパターン: 特定の型に一致するかどうかを判定します。
- 定数パターン: 特定の値に一致するかどうかを判定します。
- var パターン: 常に一致し、変数を宣言して値を割り当てます。
- 破棄パターン (
_
): どのような値にも一致し、変数を宣言しません。 - プロパティパターン: オブジェクトのプロパティの値に基づいて一致判定を行います。
- 位置パターン: タプルやオブジェクトの分解に基づいて一致判定を行います。
- リストパターン (C# 11 以降): リストや配列の要素に基づいて一致判定を行います。
- 論理パターン (C# 9 以降):
and
,or
,not
を使用して複数のパターンを組み合わせます。 - リレーショナルパターン (C# 9 以降): 関係演算子 (
<
,>
,<=
,>=
,==
,!=
) を使用して値を比較します。 - 属性パターン (C# 11 以降): カスタム属性の存在と値に基づいて一致判定を行います。
これらの機能強化により、パターンマッチングは、より複雑な条件分岐をより簡潔に記述できるようになり、C# コードの表現力を大きく向上させました。
3. パターンマッチングの具体的な活用例
パターンマッチングは、さまざまなシナリオで活用できます。以下に、いくつかの具体的な例を示します。
- 型の判定とキャスト:
“`csharp
object obj = “Hello, World!”;
if (obj is string message)
{
Console.WriteLine($”The message is: {message.ToUpper()}”);
}
else if (obj is int number)
{
Console.WriteLine($”The number is: {number * 2}”);
}
else
{
Console.WriteLine(“Unknown object type.”);
}
“`
この例では、obj
の型に基づいて異なる処理を実行しています。is string message
は、obj
が string
型の場合に message
変数にキャストし、その値を Console.WriteLine
で使用します。
- プロパティによる条件分岐:
“`csharp
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
Person person = new Person { FirstName = “John”, LastName = “Doe”, Age = 30 };
if (person is { FirstName: “John”, Age: > 18 })
{
Console.WriteLine(“John is an adult.”);
}
“`
この例では、person
オブジェクトの FirstName
と Age
プロパティに基づいて条件分岐を行っています。FirstName: "John", Age: > 18
は、FirstName
が “John” であり、Age
が 18 より大きい場合に一致します。
- タプルによる条件分岐:
“`csharp
(string name, int age) person = (“Jane”, 25);
switch (person)
{
case (“Jane”, 25):
Console.WriteLine(“Jane is 25 years old.”);
break;
case (string n, > 30):
Console.WriteLine($”{n} is older than 30.”);
break;
default:
Console.WriteLine(“Unknown person.”);
break;
}
“`
この例では、タプル person
の値に基づいて条件分岐を行っています。case ("Jane", 25):
は、person
が (“Jane”, 25) の場合に一致します。case (string n, > 30):
は、person
の年齢が 30 より大きい場合に一致し、名前を n
変数に割り当てます。
- リストパターン (C# 11 以降):
“`csharp
int[] numbers = { 1, 2, 3, 4, 5 };
switch (numbers)
{
case [1, 2, 3, , ]:
Console.WriteLine(“The first three numbers are 1, 2, and 3.”);
break;
case [, , _, 4, 5]:
Console.WriteLine(“The last two numbers are 4 and 5.”);
break;
case [var a, var b, ..]:
Console.WriteLine($”The first two numbers are {a} and {b}.”);
break;
default:
Console.WriteLine(“Unknown number sequence.”);
break;
}
“`
この例では、配列 numbers
の要素に基づいて条件分岐を行っています。case [1, 2, 3, _, _]:
は、最初の 3 つの要素が 1, 2, 3 であれば一致します。_
は破棄パターンであり、特定の値に一致する必要はありません。case [var a, var b, ..]:
は、最初の 2 つの要素を a
と b
に割り当て、..
は残りの要素を無視します。
- 論理パターン:
“`csharp
int age = 20;
if (age is >= 18 and <= 65)
{
Console.WriteLine(“The person is an adult but not a senior citizen.”);
}
string name = “John Doe”;
if (name is not null and not “”)
{
Console.WriteLine(“The name is valid.”);
}
“`
この例では、and
と not
を使用して複数の条件を組み合わせています。age is >= 18 and <= 65
は、age
が 18 以上かつ 65 以下の場合に一致します。name is not null and not ""
は、name
が null
ではなく、空文字列でもない場合に一致します。
- リレーショナルパターン:
“`csharp
int temperature = 25;
if (temperature is > 20)
{
Console.WriteLine(“It’s warm.”);
}
else if (temperature is < 10)
{
Console.WriteLine(“It’s cold.”);
}
“`
この例では、関係演算子 >
と <
を使用して値を比較しています。temperature is > 20
は、temperature
が 20 より大きい場合に一致します。
- 属性パターン (C# 11 以降):
“`csharp
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttribute : Attribute
{
public string Value { get; set; }
}
[My(Value = “Important”)]
public class MyClass
{
// …
}
Type type = typeof(MyClass);
if (type is [MyAttribute(Value: “Important”)])
{
Console.WriteLine(“MyClass has the Important attribute.”);
}
“`
この例では、カスタム属性 MyAttribute
の存在と値に基づいて一致判定を行っています。type is [MyAttribute(Value: "Important")]
は、type
が MyAttribute
属性を持ち、その Value
プロパティが “Important” である場合に一致します。
4. switch
式の進化
パターンマッチングは、switch
文だけでなく、switch
式でも利用できます。switch
式は、C# 8 で導入され、switch
文よりも簡潔で表現力豊かに条件分岐を記述できます。
“`csharp
int number = 2;
string result = number switch
{
1 => “One”,
2 => “Two”,
3 => “Three”,
_ => “Other”
};
Console.WriteLine(result); // Output: Two
“`
switch
式は、式であるため、値を返すことができます。上記の例では、number
の値に基づいて異なる文字列を result
変数に割り当てています。
switch
式は、パターンマッチングと組み合わせることで、さらに強力になります。
“`csharp
public class Shape
{
public double Area { get; set; }
}
public class Circle : Shape
{
public double Radius { get; set; }
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
}
Shape shape = new Circle { Radius = 5 };
string shapeType = shape switch
{
Circle { Radius: > 5 } => “Large Circle”,
Rectangle { Width: > 10, Height: > 5 } => “Large Rectangle”,
_ => “Other Shape”
};
Console.WriteLine(shapeType);
“`
この例では、shape
オブジェクトの型とプロパティに基づいて異なる文字列を shapeType
変数に割り当てています。
5. パフォーマンスへの影響
パターンマッチングは、コードの可読性、保守性を向上させる一方で、パフォーマンスに影響を与える可能性があります。
- 型の判定: パターンマッチングは、実行時に型の判定を行うため、従来の型チェックよりもオーバーヘッドが発生する可能性があります。
- 複雑なパターン: 複雑なパターンほど、一致判定に時間がかかる可能性があります。
しかし、C# コンパイラは、パターンマッチングのパフォーマンスを最適化するために、さまざまな手法を使用しています。
- 分岐テーブル:
switch
文やswitch
式で使用されるパターンが定数の場合、コンパイラは分岐テーブルを生成し、高速なジャンプを実現します。 - インライン展開: 簡単なパターンは、インライン展開されることでオーバーヘッドを削減できます。
パフォーマンスが重要な場合は、プロファイリングツールを使用して、パターンマッチングがボトルネックになっていないか確認する必要があります。
6. パターンマッチングを使用する上での注意点
パターンマッチングを使用する際には、以下の点に注意する必要があります。
- 可読性: パターンが複雑すぎると、コードの可読性が低下する可能性があります。パターンを簡潔に保ち、必要に応じてコメントを追加することを検討してください。
- 網羅性:
switch
文やswitch
式を使用する場合、すべての可能なパターンを網羅する必要があります。網羅されていない場合、コンパイラは警告を発行します。 - 順序:
switch
文やswitch
式で使用されるパターンの順序は重要です。一致する可能性のあるパターンを最初に記述する必要があります。
7. まとめ
C# 9 以降のパターンマッチングは、開発者の生産性を高め、安全なコードを書くための強力なツールです。型の判定、プロパティによる条件分岐、タプル、リスト、論理演算子、リレーショナル演算子、属性など、さまざまなパターンを利用できます。
パターンマッチングは、switch
文だけでなく、switch
式でも利用できます。switch
式は、switch
文よりも簡潔で表現力豊かに条件分岐を記述できます。
パターンマッチングを使用する際には、可読性、網羅性、順序に注意する必要があります。パフォーマンスが重要な場合は、プロファイリングツールを使用して、パターンマッチングがボトルネックになっていないか確認する必要があります。
パターンマッチングを効果的に活用することで、C# コードの可読性、保守性を向上させ、より効率的な開発を実現できます。
8. 今後の展望
C# は、今後もパターンマッチングの機能を拡張していくことが予想されます。例えば、以下のような機能が検討されています。
- より高度なリストパターン: リストの長さに基づいて一致判定を行う機能や、特定の条件を満たす要素を抽出する機能などが考えられます。
- カスタムパターンの定義: 開発者が独自のパターンを定義できるようにする機能が考えられます。これにより、特定のドメインに特化したパターンマッチングを実装できるようになります。
これらの機能強化により、パターンマッチングは、C# の主要な機能の一つとして、さらに重要な役割を果たすことが期待されます。
本記事が、C# のパターンマッチングに関する理解を深め、日々の開発に役立てる一助となれば幸いです。