C#新機能:型パターンマッチングの使い方完全ガイド

C#の新しい型パターンマッチング機能は、コードの可読性と効率を向上させるために設計されています。本記事では、この強力な機能の基本から応用まで、詳しく解説します。具体的なコード例や実践的な応用例を通じて、C#の型パターンマッチングを効果的に活用する方法を学びましょう。

目次

型パターンマッチングとは?

型パターンマッチングは、オブジェクトの型に基づいて条件分岐を行う方法です。これにより、異なる型に対する処理を簡潔に記述できます。従来の「if-else」文や「switch」文では、型チェックやキャストが煩雑になりがちですが、型パターンマッチングを使うことで、これらのコードをよりシンプルで読みやすく書くことができます。

基本概念

型パターンマッチングは、変数が特定の型に一致するかどうかをチェックし、一致する場合にはその型にキャストされた変数を利用できます。これにより、型チェックとキャストを一つのステップで行うことが可能です。

利点

  • 可読性の向上: 型チェックとキャストが一つの文で行えるため、コードがシンプルになります。
  • 効率性の向上: 不要な型チェックやキャストを避けることで、パフォーマンスが向上します。
  • メンテナンスの容易さ: コードが明確になり、バグの発見や修正がしやすくなります。

次のセクションでは、型パターンマッチングの基本的な使い方を具体的なコード例とともに紹介します。

基本的な使い方

型パターンマッチングの基本的な使い方を理解するために、まずはシンプルな例を見てみましょう。

基本文法

型パターンマッチングを用いたコードは、以下のように記述します。

if (obj is string s)
{
    Console.WriteLine($"String length is {s.Length}");
}
else if (obj is int i)
{
    Console.WriteLine($"Integer value is {i}");
}
else
{
    Console.WriteLine("Unknown type");
}

このコードでは、変数 obj の型をチェックし、string 型の場合には変数 s にキャストされ、int 型の場合には変数 i にキャストされます。それぞれの型に応じた処理が行われます。

「switch」文との併用

型パターンマッチングは、「switch」文でも効果的に使えます。以下にその例を示します。

switch (obj)
{
    case string s:
        Console.WriteLine($"String length is {s.Length}");
        break;
    case int i:
        Console.WriteLine($"Integer value is {i}");
        break;
    default:
        Console.WriteLine("Unknown type");
        break;
}

このコードは、先ほどの「if-else」文と同様の動作を「switch」文を用いて実現しています。「switch」文を使うことで、条件分岐がさらに明確になり、可読性が向上します。

デコンストラクションパターン

型パターンマッチングは、デコンストラクションパターンとも併用できます。以下の例では、タプルを分解して各要素にアクセスしています。

(object a, object b) tuple = (1, "hello");

if (tuple is (int i, string s))
{
    Console.WriteLine($"Integer: {i}, String: {s}");
}
else
{
    Console.WriteLine("Unknown tuple");
}

このコードでは、タプルの各要素が int 型と string 型であるかどうかをチェックし、一致する場合にはそれぞれの変数 is にキャストされています。

次のセクションでは、「switch」ステートメントでの型パターンマッチングの具体的な活用方法について詳しく見ていきます。

「switch」ステートメントでの活用

「switch」ステートメントにおける型パターンマッチングの活用方法について解説します。「switch」ステートメントは、複数の条件分岐をシンプルかつ明確に記述するために使用されます。型パターンマッチングを組み合わせることで、さらに柔軟で強力な条件分岐を実現できます。

基本的な使い方

型パターンマッチングを利用した「switch」ステートメントの基本的な使い方を以下に示します。

object obj = GetSomeObject();

switch (obj)
{
    case int i:
        Console.WriteLine($"Integer: {i}");
        break;
    case string s:
        Console.WriteLine($"String: {s}");
        break;
    case null:
        Console.WriteLine("Object is null");
        break;
    default:
        Console.WriteLine("Unknown type");
        break;
}

このコードでは、obj の型に応じて異なる処理を行います。int 型の場合は変数 istring 型の場合は変数 s にキャストされ、それぞれ対応する処理が実行されます。

プロパティパターンの活用

プロパティパターンを使用すると、オブジェクトのプロパティに基づいた条件分岐が可能になります。

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

object obj = new Person { Name = "Alice", Age = 30 };

switch (obj)
{
    case Person { Name: "Alice" }:
        Console.WriteLine("Hello Alice!");
        break;
    case Person { Age: < 18 }:
        Console.WriteLine("Minor detected");
        break;
    default:
        Console.WriteLine("Unknown person");
        break;
}

このコードでは、Person オブジェクトの Name プロパティと Age プロパティに基づいて条件分岐を行っています。

結合パターンの利用

複数のパターンを組み合わせて、より詳細な条件分岐を行うことも可能です。

object obj = GetSomeObject();

switch (obj)
{
    case int i when i > 0:
        Console.WriteLine("Positive integer");
        break;
    case int i when i < 0:
        Console.WriteLine("Negative integer");
        break;
    case string s when s.Length > 0:
        Console.WriteLine("Non-empty string");
        break;
    case string s:
        Console.WriteLine("Empty string");
        break;
    default:
        Console.WriteLine("Unknown type or value");
        break;
}

このコードでは、when キーワードを使って条件をさらに絞り込んでいます。正の整数、負の整数、非空文字列、空文字列に応じた処理を行います。

次のセクションでは、プロパティパターンの利用方法について詳しく解説します。

プロパティパターンの利用

プロパティパターンは、オブジェクトの特定のプロパティに基づいて条件分岐を行う強力な方法です。これにより、オブジェクトの内部状態に基づいた柔軟なマッチングが可能になります。

基本的なプロパティパターン

プロパティパターンの基本的な使い方を以下に示します。

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

object obj = new Person { Name = "Bob", Age = 25 };

if (obj is Person { Name: "Bob" })
{
    Console.WriteLine("This is Bob.");
}
else if (obj is Person { Age: 25 })
{
    Console.WriteLine("This person is 25 years old.");
}
else
{
    Console.WriteLine("Unknown person.");
}

このコードでは、Person オブジェクトの Name プロパティと Age プロパティに基づいて条件分岐を行っています。

ネストされたプロパティパターン

プロパティパターンはネストすることも可能です。これにより、複雑なオブジェクト構造に対しても条件分岐を行うことができます。

public class Address
{
    public string City { get; set; }
    public string Street { get; set; }
}

public class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

object obj = new Person 
{ 
    Name = "Alice", 
    Address = new Address { City = "New York", Street = "5th Avenue" } 
};

if (obj is Person { Address: { City: "New York" } })
{
    Console.WriteLine("This person lives in New York.");
}
else
{
    Console.WriteLine("This person does not live in New York.");
}

このコードでは、Person オブジェクトの Address プロパティに含まれる City プロパティに基づいて条件分岐を行っています。

プロパティパターンと「switch」文の併用

プロパティパターンは「switch」文と組み合わせることで、さらに柔軟な条件分岐が可能です。

object obj = new Person 
{ 
    Name = "Alice", 
    Address = new Address { City = "New York", Street = "5th Avenue" } 
};

switch (obj)
{
    case Person { Address: { City: "New York" } }:
        Console.WriteLine("This person lives in New York.");
        break;
    case Person { Address: { City: "Los Angeles" } }:
        Console.WriteLine("This person lives in Los Angeles.");
        break;
    default:
        Console.WriteLine("Unknown city.");
        break;
}

このコードでは、Person オブジェクトの Address プロパティに含まれる City プロパティに基づいて条件分岐を行い、異なるメッセージを出力しています。

次のセクションでは、レコード型と型パターンマッチングの組み合わせについて解説します。

レコード型とパターンマッチング

C# 9.0 で導入されたレコード型は、パターンマッチングと非常に相性が良いです。レコード型は、不変のデータを扱うためのシンタックスシュガーを提供し、データクラスを簡潔に表現するための便利な機能を持っています。

レコード型の基本

レコード型は、以下のように定義します。

public record Person(string Name, int Age);

このコードで、Person レコードは不変のデータクラスとして定義されます。これにより、コンストラクタ、デコンストラクタ、Equals メソッド、および GetHashCode メソッドが自動的に生成されます。

レコード型のパターンマッチング

レコード型をパターンマッチングで使用する例を見てみましょう。

var person = new Person("Alice", 30);

if (person is Person(string name, int age))
{
    Console.WriteLine($"Name: {name}, Age: {age}");
}

このコードでは、Person レコードをパターンマッチングで分解し、nameage の値を取得しています。

デコンストラクションパターン

レコード型はデコンストラクションパターンとも相性が良いです。

var person = new Person("Bob", 25);

switch (person)
{
    case Person(string name, int age):
        Console.WriteLine($"Name: {name}, Age: {age}");
        break;
    default:
        Console.WriteLine("Unknown person");
        break;
}

このコードでは、switch 文を使用してレコード型 Person をデコンストラクションし、各プロパティにアクセスしています。

ネストされたレコード型のパターンマッチング

ネストされたレコード型でもパターンマッチングを使用できます。

public record Address(string City, string Street);
public record Person(string Name, int Age, Address Address);

var person = new Person("Charlie", 28, new Address("Los Angeles", "Sunset Blvd"));

if (person is Person(string name, int age, Address(string city, string street)))
{
    Console.WriteLine($"Name: {name}, Age: {age}, City: {city}, Street: {street}");
}

このコードでは、Person レコードの Address プロパティも含めてパターンマッチングを行っています。

次のセクションでは、パターンマッチングと例外処理の組み合わせについて解説します。

パターンマッチングと例外処理

パターンマッチングは、例外処理の文脈でも非常に有用です。これにより、異なるタイプの例外に対する特定の処理を簡潔に記述できます。

基本的な例外処理でのパターンマッチング

以下のコードでは、catch ブロックでパターンマッチングを使用して、異なるタイプの例外を処理しています。

try
{
    // 例外が発生する可能性のあるコード
}
catch (Exception ex) when (ex is ArgumentNullException)
{
    Console.WriteLine("Null argument passed.");
}
catch (Exception ex) when (ex is InvalidOperationException)
{
    Console.WriteLine("Invalid operation performed.");
}
catch (Exception ex)
{
    Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}

このコードでは、ArgumentNullExceptionInvalidOperationException の場合にそれぞれ異なるメッセージを表示し、それ以外の例外については一般的なメッセージを表示します。

詳細な例外情報の取得

例外のプロパティに基づいてさらに詳しい情報を取得することもできます。

try
{
    // 例外が発生する可能性のあるコード
}
catch (Exception ex)
{
    switch (ex)
    {
        case ArgumentNullException { ParamName: var paramName }:
            Console.WriteLine($"Argument null: {paramName}");
            break;
        case InvalidOperationException { Message: var message }:
            Console.WriteLine($"Invalid operation: {message}");
            break;
        default:
            Console.WriteLine($"An unexpected error occurred: {ex.GetType().Name}");
            break;
    }
}

このコードでは、ArgumentNullExceptionParamName プロパティと InvalidOperationExceptionMessage プロパティを使用して、詳細な情報を取得しています。

カスタム例外クラスの処理

カスタム例外クラスを定義し、それをパターンマッチングで処理する例も見てみましょう。

public class CustomException : Exception
{
    public int ErrorCode { get; }

    public CustomException(string message, int errorCode) : base(message)
    {
        ErrorCode = errorCode;
    }
}

try
{
    // 例外が発生する可能性のあるコード
}
catch (Exception ex)
{
    switch (ex)
    {
        case CustomException { ErrorCode: 404 }:
            Console.WriteLine("Resource not found.");
            break;
        case CustomException { ErrorCode: var code }:
            Console.WriteLine($"Custom error with code: {code}");
            break;
        default:
            Console.WriteLine($"An unexpected error occurred: {ex.Message}");
            break;
    }
}

このコードでは、CustomExceptionErrorCode プロパティに基づいて異なる処理を行っています。

次のセクションでは、実践的な応用例について解説します。実際のプロジェクトで型パターンマッチングをどのように活用できるかを具体的に見ていきましょう。

実践的な応用例

型パターンマッチングを実際のプロジェクトでどのように活用できるかを、具体的なコード例を通じて見ていきましょう。これにより、パターンマッチングの実践的な応用方法を理解できます。

APIレスポンスの処理

異なる型のAPIレスポンスを処理する際に、型パターンマッチングを使用することでコードがシンプルになります。

public class SuccessResponse
{
    public string Data { get; set; }
}

public class ErrorResponse
{
    public string Error { get; set; }
}

public class ApiResponse
{
    public object Response { get; set; }
}

ApiResponse apiResponse = GetApiResponse();

switch (apiResponse.Response)
{
    case SuccessResponse success:
        Console.WriteLine($"Success: {success.Data}");
        break;
    case ErrorResponse error:
        Console.WriteLine($"Error: {error.Error}");
        break;
    default:
        Console.WriteLine("Unknown response type.");
        break;
}

このコードでは、APIレスポンスの型に応じて異なる処理を行い、成功レスポンスとエラーレスポンスを適切に処理しています。

UI要素の動的更新

UI要素を動的に更新する際に、型パターンマッチングを使用すると、異なる型の要素を一貫して処理できます。

public class TextBox
{
    public string Text { get; set; }
}

public class Button
{
    public string Label { get; set; }
}

public class UiElement
{
    public object Element { get; set; }
}

UiElement uiElement = GetUiElement();

switch (uiElement.Element)
{
    case TextBox textBox:
        textBox.Text = "Updated Text";
        Console.WriteLine("TextBox updated.");
        break;
    case Button button:
        button.Label = "Updated Label";
        Console.WriteLine("Button updated.");
        break;
    default:
        Console.WriteLine("Unknown UI element.");
        break;
}

このコードでは、TextBoxButton の型に基づいて適切なプロパティを更新し、UI要素を動的に変更しています。

ログメッセージの処理

ログメッセージの処理においても、型パターンマッチングを使用することで、異なる型のログエントリを効率的に処理できます。

public class InfoLog
{
    public string Message { get; set; }
}

public class ErrorLog
{
    public string ErrorMessage { get; set; }
    public int ErrorCode { get; set; }
}

public class LogEntry
{
    public object Log { get; set; }
}

LogEntry logEntry = GetLogEntry();

switch (logEntry.Log)
{
    case InfoLog info:
        Console.WriteLine($"Info: {info.Message}");
        break;
    case ErrorLog error:
        Console.WriteLine($"Error: {error.ErrorMessage}, Code: {error.ErrorCode}");
        break;
    default:
        Console.WriteLine("Unknown log type.");
        break;
}

このコードでは、情報ログとエラーログをそれぞれ適切に処理し、ログメッセージを出力しています。

データ解析のパターンマッチング

データ解析において、異なるデータ型に基づいた解析処理を行う例です。

public class SensorData
{
    public double Value { get; set; }
}

public class EventData
{
    public string EventName { get; set; }
}

public class AnalysisData
{
    public object Data { get; set; }
}

AnalysisData analysisData = GetAnalysisData();

switch (analysisData.Data)
{
    case SensorData sensor:
        Console.WriteLine($"Sensor value: {sensor.Value}");
        break;
    case EventData event:
        Console.WriteLine($"Event: {event.EventName}");
        break;
    default:
        Console.WriteLine("Unknown data type.");
        break;
}

このコードでは、SensorDataEventData に基づいて異なる解析処理を行い、データの内容を出力しています。

次のセクションでは、理解を深めるための演習問題を提供します。これにより、型パターンマッチングの実践的な応用力を強化できます。

演習問題

型パターンマッチングの理解を深めるために、以下の演習問題を解いてみましょう。これらの問題を通じて、基本的な使い方から応用までを実践的に学びます。

演習問題1: 基本的な型パターンマッチング

次のコードを完成させて、objdouble 型の場合にその値を表示し、bool 型の場合にその値を表示するようにしてください。それ以外の場合は「Unknown type」と表示します。

object obj = GetSomeValue();

if (/* your pattern matching here */)
{
    Console.WriteLine($"Double value: {value}");
}
else if (/* your pattern matching here */)
{
    Console.WriteLine($"Boolean value: {value}");
}
else
{
    Console.WriteLine("Unknown type");
}

演習問題2: 「switch」文とプロパティパターン

次のコードを修正して、Person オブジェクトの Age プロパティに基づいてメッセージを表示するようにしてください。Age が18未満の場合は「Minor」、18以上の場合は「Adult」と表示します。

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Person person = new Person { Name = "John", Age = 20 };

switch (/* your pattern matching here */)
{
    case /* your case pattern here */:
        Console.WriteLine("Minor");
        break;
    case /* your case pattern here */:
        Console.WriteLine("Adult");
        break;
    default:
        Console.WriteLine("Unknown age");
        break;
}

演習問題3: ネストされたプロパティパターン

次のコードを完成させて、Order オブジェクトの Customer プロパティに基づいて条件分岐を行い、CustomerCountry が「USA」の場合に「Domestic order」、それ以外の場合に「International order」と表示するようにしてください。

public class Customer
{
    public string Name { get; set; }
    public string Country { get; set; }
}

public class Order
{
    public int OrderId { get; set; }
    public Customer Customer { get; set; }
}

Order order = new Order { OrderId = 123, Customer = new Customer { Name = "Alice", Country = "USA" } };

if (/* your pattern matching here */)
{
    Console.WriteLine("Domestic order");
}
else
{
    Console.WriteLine("International order");
}

演習問題4: カスタム例外の処理

次のコードを完成させて、CustomException を処理する catch ブロックを追加し、ErrorCode プロパティが500の場合に「Server error」と表示し、それ以外の場合に「Application error」と表示するようにしてください。

public class CustomException : Exception
{
    public int ErrorCode { get; }

    public CustomException(string message, int errorCode) : base(message)
    {
        ErrorCode = errorCode;
    }
}

try
{
    // Code that may throw exceptions
}
catch (/* your pattern matching here */)
{
    if (/* your condition here */)
    {
        Console.WriteLine("Server error");
    }
    else
    {
        Console.WriteLine("Application error");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}

これらの演習問題を解くことで、型パターンマッチングの実践的なスキルを身につけることができます。次のセクションでは、本記事のまとめを行います。

まとめ

型パターンマッチングは、C#の最新機能の一つであり、コードの可読性と効率性を大幅に向上させます。基本的な使い方から、プロパティパターン、レコード型との組み合わせ、例外処理、実践的な応用例まで幅広く解説しました。

この機能を活用することで、複雑な条件分岐をシンプルかつ明確に記述できるようになります。これにより、メンテナンス性が向上し、バグの発見や修正も容易になります。

さらに、演習問題を通じて実践的なスキルを強化し、具体的なプロジェクトでの応用力を高めることができます。今後のプロジェクトで、ぜひ型パターンマッチングを活用してみてください。

この記事が、型パターンマッチングを理解し、実際に活用するための一助となれば幸いです。

コメント

コメントする

目次