C# caseの完全ガイド:基礎から最新機能まで解説する15の実践テクニック

はじめに

C#のswitch-case文は、条件分岐を扱う基本的な制御構造でありながら、モダンなC#では強力なパターンマッチング機能を備えた表現力豊かな機能へと進化しています。
本記事では、基礎から実践的な使用方法まで、具体的なコード例を交えて解説します。

本記事で学べること

switch-case文の基本的な構文と使用方法

C# 8.0以降で導入されたモダンなパターンマッチング機能

パフォーマンスを考慮したswitch-case文の実装方法

実務で活用できる具体的なユースケースと実装パターン

スレッドセーフな実装方法とベストプラクティス

APIレスポンス処理やワークフロー管理での実践的な活用方法

C# caseステートメントの基礎知識

switch-case文の基本構文と動作原理

switch-case文は、値に基づいて処理を分岐させる制御構造です。C#では、従来のswitch文と、より簡潔なswitch式の2つの形式を使用できます。

基本的な使用例

public class WeekdayAnalyzer
{
    /// <summary>
    /// 曜日の種類を判定します
    /// </summary>
    /// <param name="day">判定対象の曜日</param>
    /// <returns>曜日の種類を示す文字列</returns>
    public string GetDayType(DayOfWeek day) =>
        day switch
        {
            DayOfWeek.Saturday or DayOfWeek.Sunday => "休日",
            DayOfWeek.Friday => "金曜日",
            _ => "平日"
        };

    /// <summary>
    /// メッセージの種類に応じた処理を行います
    /// </summary>
    /// <param name="messageType">メッセージの種類</param>
    /// <param name="content">メッセージの内容</param>
    /// <returns>処理結果のメッセージ</returns>
    public string ProcessMessage(string messageType, string content)
    {
        if (string.IsNullOrEmpty(messageType))
            throw new ArgumentNullException(nameof(messageType));

        switch (messageType.ToLower())
        {
            case "error":
                LogError(content);
                return "エラーを記録しました";

            case "warning":
                LogWarning(content);
                return "警告を記録しました";

            case "info":
                LogInfo(content);
                return "情報を記録しました";

            default:
                throw new ArgumentException("未対応のメッセージ種別です", nameof(messageType));
        }
    }

    private void LogError(string content) => Console.WriteLine($"Error: {content}");
    private void LogWarning(string content) => Console.WriteLine($"Warning: {content}");
    private void LogInfo(string content) => Console.WriteLine($"Info: {content}");
}

C#のバージョンアップに伴う主要な機能強化

public class ModernSwitchDemo
{
    /// <summary>
    /// 数値を分析し、その特性を返します
    /// </summary>
    /// <param name="value">分析対象の数値</param>
    /// <returns>数値の特性を示す文字列</returns>
    public string AnalyzeNumber(int value) =>
        value switch
        {
            < 0 => "負の数",
            0 => "ゼロ",
            > 0 and <= 10 => "1桁の正の数",
            > 10 and <= 99 => "2桁の正の数",
            _ => "3桁以上の正の数"
        };

    /// <summary>
    /// オブジェクトの型と値を分析します
    /// </summary>
    /// <param name="obj">分析対象のオブジェクト</param>
    /// <returns>分析結果の文字列</returns>
    public string AnalyzeObject(object obj) =>
        obj switch
        {
            null => "null値",
            string { Length: 0 } => "空文字列",
            string s => $"文字列: {s}",
            int n when n % 2 == 0 => $"偶数: {n}",
            int n => $"奇数: {n}",
            DateTime d => $"日付: {d:yyyy/MM/dd}",
            _ => $"その他の型: {obj.GetType().Name}"
        };
}

caseステートメントの重要性

1. コードの可読性

public class StatusProcessor
{
    public enum OrderStatus { New, Processing, Shipped, Delivered }

    /// <summary>
    /// 注文ステータスに応じたメッセージを生成します
    /// </summary>
    /// <param name="status">注文ステータス</param>
    /// <returns>ステータスに対応するメッセージ</returns>
    public string GetStatusMessage(OrderStatus status) =>
        status switch
        {
            OrderStatus.New => "新規注文を受け付けました",
            OrderStatus.Processing => "注文を処理中です",
            OrderStatus.Shipped => "商品を発送しました",
            OrderStatus.Delivered => "配送が完了しました",
            _ => throw new ArgumentException("無効な注文ステータスです", nameof(status))
        };
}

2. パターンマッチングの活用

public class ShapeCalculator
{
    public record Circle(double Radius);
    public record Rectangle(double Width, double Height);
    public record Triangle(double Base, double Height);

    /// <summary>
    /// 図形の面積を計算します
    /// </summary>
    /// <param name="shape">計算対象の図形</param>
    /// <returns>図形の面積</returns>
    public double CalculateArea(object shape) =>
        shape switch
        {
            Circle c => Math.PI * c.Radius * c.Radius,
            Rectangle r => r.Width * r.Height,
            Triangle t => t.Base * t.Height / 2,
            _ => throw new ArgumentException("未対応の図形です", nameof(shape))
        };
}

この基本的な構文と機能を理解することで、より高度なパターンマッチングや実践的なユースケースでの活用が可能になります。

switch-case文の実践的な使い方

実践的なバリデーションの実装

フォーム入力やAPIリクエストのバリデーションは、実務でよく遭遇するシナリオです。

public class ValidationEngine
{
    public record ValidationResult(bool IsValid, string Message);

    /// <summary>
    /// ユーザー入力データを検証します
    /// </summary>
    /// <param name="fieldName">検証対象のフィールド名</param>
    /// <param name="value">検証する値</param>
    /// <returns>検証結果</returns>
    public ValidationResult Validate(string fieldName, object value)
    {
        if (string.IsNullOrEmpty(fieldName))
            throw new ArgumentNullException(nameof(fieldName));

        return (fieldName.ToLower(), value) switch
        {
            ("email", string email) => ValidateEmail(email),
            ("password", string password) => ValidatePassword(password),
            ("age", int age) => ValidateAge(age),
            ("username", string username) => ValidateUsername(username),
            _ => new ValidationResult(false, $"未対応のフィールド: {fieldName}")
        };
    }

    private ValidationResult ValidateEmail(string email) =>
        email switch
        {
            null => new(false, "メールアドレスは必須です"),
            "" => new(false, "メールアドレスは必須です"),
            var e when !e.Contains("@") => new(false, "メールアドレスの形式が不正です"),
            var e when e.Length > 256 => new(false, "メールアドレスが長すぎます"),
            _ => new(true, "有効なメールアドレスです")
        };

    private ValidationResult ValidatePassword(string password) =>
        password switch
        {
            null => new(false, "パスワードは必須です"),
            "" => new(false, "パスワードは必須です"),
            var p when p.Length < 8 => new(false, "パスワードは8文字以上である必要があります"),
            var p when !p.Any(char.IsUpper) => new(false, "パスワードには大文字を含める必要があります"),
            var p when !p.Any(char.IsLower) => new(false, "パスワードには小文字を含める必要があります"),
            var p when !p.Any(char.IsDigit) => new(false, "パスワードには数字を含める必要があります"),
            _ => new(true, "有効なパスワードです")
        };

    private ValidationResult ValidateAge(int age) =>
        age switch
        {
            < 0 => new(false, "年齢は0以上である必要があります"),
            0 => new(false, "年齢は必須です"),
            > 120 => new(false, "入力された年齢が上限を超えています"),
            _ => new(true, "有効な年齢です")
        };

    private ValidationResult ValidateUsername(string username) =>
        username switch
        {
            null => new(false, "ユーザー名は必須です"),
            "" => new(false, "ユーザー名は必須です"),
            var u when u.Length < 3 => new(false, "ユーザー名は3文字以上である必要があります"),
            var u when u.Length > 20 => new(false, "ユーザー名は20文字以下である必要があります"),
            var u when !u.All(c => char.IsLetterOrDigit(c) || c == '_') 
                => new(false, "ユーザー名には英数字とアンダースコアのみ使用できます"),
            _ => new(true, "有効なユーザー名です")
        };
}

ビジネスルールの実装

複雑なビジネスルールを見通しよく実装する例を示します。

public class DiscountCalculator
{
    public record Customer(
        string Tier,
        int PurchaseCount,
        decimal TotalSpent,
        DateTime RegistrationDate);

    public record DiscountResult(
        decimal DiscountRate,
        string Reason,
        bool IsSpecialOffer);

    /// <summary>
    /// 顧客の割引率を計算します
    /// </summary>
    /// <param name="customer">顧客情報</param>
    /// <returns>割引率の計算結果</returns>
    public DiscountResult CalculateDiscount(Customer customer)
    {
        if (customer == null)
            throw new ArgumentNullException(nameof(customer));

        // ロイヤリティに基づく割引
        var loyaltyDiscount = customer.Tier switch
        {
            "プラチナ" => new DiscountResult(0.20m, "プラチナ会員特典", false),
            "ゴールド" => new DiscountResult(0.15m, "ゴールド会員特典", false),
            "シルバー" => new DiscountResult(0.10m, "シルバー会員特典", false),
            _ => new DiscountResult(0.05m, "通常会員特典", false)
        };

        // 購入履歴に基づく特別割引
        var historyDiscount = (customer.PurchaseCount, customer.TotalSpent) switch
        {
            ( >= 100, >= 1000000m) => new DiscountResult(0.25m, "VIP特別割引", true),
            ( >= 50, >= 500000m) => new DiscountResult(0.20m, "優良顧客割引", true),
            ( >= 30, >= 300000m) => new DiscountResult(0.15m, "常連割引", true),
            _ => loyaltyDiscount
        };

        // 会員登録期間に基づく追加割引
        var membershipYears = (DateTime.Now - customer.RegistrationDate).Days / 365;
        return membershipYears switch
        {
            >= 5 => new DiscountResult(
                Math.Max(historyDiscount.DiscountRate, 0.20m),
                "5年以上の継続会員特典",
                true),
            >= 3 => new DiscountResult(
                Math.Max(historyDiscount.DiscountRate, 0.15m),
                "3年以上の継続会員特典",
                true),
            _ => historyDiscount
        };
    }
}

エラー処理とログ出力

エラー処理とログ出力を効率的に実装する例を示します。

public class ErrorHandler
{
    public enum LogLevel { Debug, Info, Warning, Error, Critical }

    /// <summary>
    /// エラーを処理し、適切なログを出力します
    /// </summary>
    /// <param name="error">発生したエラー</param>
    /// <param name="context">エラーのコンテキスト情報</param>
    public void HandleError(Exception error, string context)
    {
        if (error == null)
            throw new ArgumentNullException(nameof(error));

        var (level, message) = error switch
        {
            ArgumentException ae => (
                LogLevel.Warning,
                $"引数エラー - {context}: {ae.Message}"),

            InvalidOperationException ioe => (
                LogLevel.Error,
                $"操作エラー - {context}: {ioe.Message}"),

            HttpRequestException hre => (
                LogLevel.Error,
                $"HTTP通信エラー - {context}: {hre.Message}"),

            FileNotFoundException fnf => (
                LogLevel.Critical,
                $"ファイル未検出 - {context}: {fnf.Message}"),

            _ => (
                LogLevel.Error,
                $"未分類のエラー - {context}: {error.Message}")
        };

        var logMessage = level switch
        {
            LogLevel.Debug => $"[DEBUG] {message}",
            LogLevel.Info => $"[INFO] {message}",
            LogLevel.Warning => $"[WARN] {message}",
            LogLevel.Error => $"[ERROR] {message}",
            LogLevel.Critical => $"[CRIT] {message}",
            _ => $"[UNKNOWN] {message}"
        };

        LogMessage(level, logMessage);
        NotifyIfNeeded(level, message);
    }

    private void LogMessage(LogLevel level, string message)
    {
        // ログ出力の実装
        Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {message}");
    }

    private void NotifyIfNeeded(LogLevel level, string message)
    {
        if (level >= LogLevel.Error)
        {
            // 重大なエラーの場合は管理者に通知
            Console.WriteLine($"管理者通知: {message}");
        }
    }
}

これらの実装例は、実務で直面する典型的なシナリオにおいてswitch-case文を効果的に活用する方法を示しています。
各実装は以下を備えています。

  • 適切な例外処理
  • わかりやすいエラーメッセージ
  • 保守性の高いコード構造

C#のモダンなパターンマッチング機能

高度なパターンマッチング技法

C# 10.0以降で導入された高度なパターンマッチング機能を活用することで、より表現力豊かなコードを記述できます。

1. プロパティパターンの高度な使用法

public class AdvancedPatternMatcher
{
    public record User(string Name, int Age, Address Address, List<Order> Orders);
    public record Address(string Country, string City, string PostalCode);
    public record Order(decimal Amount, DateTime OrderDate);

    public string AnalyzeUser(User user)
    {
        return user switch
        {
            // ネストされたプロパティパターン
            { Address: { Country: "日本" }, Orders: { Count: > 10 } }
                => "日本の優良顧客",

            // コレクションパターン
            { Orders: [{ Amount: > 100000 }, ..] }
                => "大口注文あり",

            // 複合条件
            {
                Age: >= 20,
                Address: { Country: "日本" },
                Orders: var orders
            } when orders.Sum(o => o.Amount) > 50000
                => "日本の成人優良顧客",

            _ => "通常顧客"
        };
    }
}

2. 拡張プロパティパターン

public class ExtendedPatternExample
{
    public record Temperature(decimal Value, string Scale);
    public record WeatherData(
        Temperature Current,
        Temperature High,
        Temperature Low,
        decimal Humidity,
        decimal WindSpeed);

    public string AnalyzeWeather(WeatherData data)
    {
        return data switch
        {
            // 複数の条件を組み合わせた高度なパターン
            {
                Current.Value: > 30,
                Humidity: > 80,
                WindSpeed: < 5
            } => "蒸し暑い",

            {
                Current.Value: < 0,
                WindSpeed: > 10
            } => "厳寒注意",

            {
                High.Value: > 35,
                Low.Value: > 25
            } => "熱帯夜注意",

            _ => "通常気象"
        };
    }
}

3. タプルパターンの高度な活用

public class AdvancedTuplePatterns
{
    public record StockData(string Symbol, decimal Price, decimal PreviousClose);

    public string AnalyzeStockMovement(StockData current, StockData previous)
    {
        return (current, previous) switch
        {
            // 急激な価格上昇
            ({ Price: var cp, Symbol: var s1 },
             { Price: var pp, Symbol: var s2 })
                when s1 == s2 && cp > pp * 1.1m
                => "急上昇",

            // 急激な価格下落
            ({ Price: var cp, Symbol: var s1 },
             { Price: var pp, Symbol: var s2 })
                when s1 == s2 && cp < pp * 0.9m
                => "急下落",

            // 横ばい
            ({ Price: var cp, Symbol: var s1 },
             { Price: var pp, Symbol: var s2 })
                when s1 == s2 && Math.Abs(cp - pp) / pp < 0.01m
                => "横ばい",

            _ => "通常変動"
        };
    }

    public string AnalyzeMarketTrend(
        decimal openPrice,
        decimal highPrice,
        decimal lowPrice,
        decimal closePrice)
    {
        return (openPrice, highPrice, lowPrice, closePrice) switch
        {
            var (o, h, l, c) when c > o && (h - l) / o < 0.01m
                => "安定上昇",

            var (o, h, l, c) when c < o && (h - l) / o < 0.01m
                => "安定下落",

            var (o, h, l, c) when c > o && (h - l) / o > 0.05m
                => "不安定な上昇",

            var (o, h, l, c) when c < o && (h - l) / o > 0.05m
                => "不安定な下落",

            _ => "不明確なトレンド"
        };
    }
}

4. リストパターンの活用

public class ListPatternExample
{
    public record TransactionRecord(decimal Amount, DateTime Date, string Type);

    public string AnalyzeTransactionPattern(List<TransactionRecord> transactions)
    {
        return transactions switch
        {
            [] => "取引なし",
            
            [var single] => $"単独取引: {single.Amount}円",
            
            [var first, var second]
                when first.Amount == second.Amount
                => "同額連続取引",
            
            [_, _, .. var rest, var last]
                when rest.All(t => t.Amount > last.Amount)
                => "減少傾向",
            
            [var first, .., var last]
                when last.Amount > first.Amount * 2
                => "大幅な増加傾向",
            
            _ => "通常パターン"
        };
    }
}

これらの高度なパターンマッチング機能を活用することで、複雑なビジネスロジックを簡潔かつ可読性の高いコードとして実装できます。

パフォーマンスとベストプラクティス

パフォーマンス最適化の具体例

実際のパフォーマンス計測とその改善方法を示します。

using System.Diagnostics;

public class SwitchPerformanceDemo
{
    private readonly Dictionary<string, string> _commandCache = new(StringComparer.OrdinalIgnoreCase);
    private const int IterationCount = 1000000;

    public SwitchPerformanceDemo()
    {
        _commandCache["help"] = "ヘルプを表示します";
        _commandCache["version"] = "バージョンを表示します";
        _commandCache["exit"] = "アプリケーションを終了します";
        _commandCache["status"] = "状態を表示します";
    }

    /// <summary>
    /// 異なる実装方法のパフォーマンスを計測します
    /// </summary>
    public void RunPerformanceTest()
    {
        var stopwatch = new Stopwatch();
        var command = "help";

        // 通常のswitch文でのパフォーマンス計測
        stopwatch.Start();
        for (int i = 0; i < IterationCount; i++)
        {
            _ = ProcessCommandWithSwitch(command);
        }
        stopwatch.Stop();
        Console.WriteLine($"Switch文の実行時間: {stopwatch.ElapsedMilliseconds}ms");

        // Dictionary使用時のパフォーマンス計測
        stopwatch.Restart();
        for (int i = 0; i < IterationCount; i++)
        {
            _ = ProcessCommandWithDictionary(command);
        }
        stopwatch.Stop();
        Console.WriteLine($"Dictionary使用時の実行時間: {stopwatch.ElapsedMilliseconds}ms");

        // switch式でのパフォーマンス計測
        stopwatch.Restart();
        for (int i = 0; i < IterationCount; i++)
        {
            _ = ProcessCommandWithSwitchExpression(command);
        }
        stopwatch.Stop();
        Console.WriteLine($"Switch式の実行時間: {stopwatch.ElapsedMilliseconds}ms");
    }

    // 通常のswitch文による実装
    private string ProcessCommandWithSwitch(string command)
    {
        switch (command.ToLower())
        {
            case "help":
                return "ヘルプを表示します";
            case "version":
                return "バージョンを表示します";
            case "exit":
                return "アプリケーションを終了します";
            case "status":
                return "状態を表示します";
            default:
                return "不明なコマンドです";
        }
    }

    // Dictionary使用による実装
    private string ProcessCommandWithDictionary(string command)
    {
        return _commandCache.TryGetValue(command, out var result) 
            ? result 
            : "不明なコマンドです";
    }

    // switch式による実装
    private string ProcessCommandWithSwitchExpression(string command) =>
        command.ToLower() switch
        {
            "help" => "ヘルプを表示します",
            "version" => "バージョンを表示します",
            "exit" => "アプリケーションを終了します",
            "status" => "状態を表示します",
            _ => "不明なコマンドです"
        };
}

メモリ効率を考慮した実装

メモリ使用量を最適化する実装例を示します。

public class MemoryEfficientProcessor
{
    // 値型を使用したenum定義
    public enum ProcessingStatus : byte
    {
        Pending,
        Processing,
        Completed,
        Failed
    }

    // レコードを使用した不変オブジェクト
    public readonly record struct ProcessingResult(
        ProcessingStatus Status,
        string Message)
    {
        // カスタムな文字列プール
        private static readonly string[] CommonMessages = {
            "処理待ち",
            "処理中",
            "処理完了",
            "処理失敗"
        };

        /// <summary>
        /// メモリ効率を考慮してステータスに応じたメッセージを生成します
        /// </summary>
        /// <param name="status">処理ステータス</param>
        /// <returns>ステータスメッセージ</returns>
        public static ProcessingResult CreateFromStatus(ProcessingStatus status)
        {
            var message = status switch
            {
                ProcessingStatus.Pending => CommonMessages[0],
                ProcessingStatus.Processing => CommonMessages[1],
                ProcessingStatus.Completed => CommonMessages[2],
                ProcessingStatus.Failed => CommonMessages[3],
                _ => throw new ArgumentException("無効なステータスです", nameof(status))
            };

            return new ProcessingResult(status, message);
        }
    }

    /// <summary>
    /// 大量のデータを効率的に処理します
    /// </summary>
    /// <param name="items">処理対象のアイテム</param>
    /// <returns>処理結果のリスト</returns>
    public IEnumerable<ProcessingResult> ProcessItems(IEnumerable<string> items)
    {
        return items.Select(item => ProcessSingleItem(item));
    }

    private ProcessingResult ProcessSingleItem(string item)
    {
        if (string.IsNullOrEmpty(item))
        {
            return ProcessingResult.CreateFromStatus(ProcessingStatus.Failed);
        }

        try
        {
            // 処理ロジックの実装
            return ProcessingResult.CreateFromStatus(ProcessingStatus.Completed);
        }
        catch
        {
            return ProcessingResult.CreateFromStatus(ProcessingStatus.Failed);
        }
    }
}

スレッドセーフな実装

マルチスレッド環境での安全な実装例を示します。

public class ThreadSafeProcessor
{
    private readonly ConcurrentDictionary<string, ProcessingStatus> _statusMap = new();

    public enum ProcessingStatus
    {
        Queued,
        Processing,
        Completed,
        Failed
    }

    /// <summary>
    /// スレッドセーフに処理ステータスを更新します
    /// </summary>
    /// <param name="id">処理ID</param>
    /// <param name="newStatus">新しいステータス</param>
    /// <returns>更新が成功したかどうか</returns>
    public bool UpdateStatus(string id, ProcessingStatus newStatus)
    {
        return _statusMap.AddOrUpdate(
            id,
            newStatus,
            (_, currentStatus) =>
            {
                // ステータス遷移の検証
                return (currentStatus, newStatus) switch
                {
                    (ProcessingStatus.Queued, ProcessingStatus.Processing) => newStatus,
                    (ProcessingStatus.Processing, ProcessingStatus.Completed) => newStatus,
                    (ProcessingStatus.Processing, ProcessingStatus.Failed) => newStatus,
                    _ => currentStatus // 無効な遷移の場合は現在のステータスを維持
                };
            }) == newStatus;
    }

    /// <summary>
    /// 非同期でタスクを処理します
    /// </summary>
    /// <param name="id">処理ID</param>
    /// <returns>処理結果</returns>
    public async Task<ProcessingStatus> ProcessAsync(string id)
    {
        if (!_statusMap.TryAdd(id, ProcessingStatus.Queued))
        {
            return _statusMap.GetValueOrDefault(id);
        }

        try
        {
            if (!UpdateStatus(id, ProcessingStatus.Processing))
            {
                return _statusMap.GetValueOrDefault(id);
            }

            await Task.Delay(100); // 実際の処理を想定

            UpdateStatus(id, ProcessingStatus.Completed);
            return ProcessingStatus.Completed;
        }
        catch
        {
            UpdateStatus(id, ProcessingStatus.Failed);
            return ProcessingStatus.Failed;
        }
    }
}
これらの実装例から得られる主要なベストプラクティス
  1. パフォーマンス最適化
    • 頻繁に使用される値はキャッシュする
    • 文字列操作は最小限に抑える
    • 適切なデータ構造を選択する
  2. メモリ効率
    • 値型を適切に活用する
    • 文字列プールを利用する
    • 不要なオブジェクト生成を避ける
  3. スレッドセーフティ
    • 適切な同期機構を使用する
    • 状態遷移を厳密に管理する
    • 競合状態を考慮した設計を行う

これらの原則に従うことで、高性能で信頼性の高い実装を実現できます。

実践的なユースケース集

APIレスポンスハンドラーの実装

REST APIのレスポンス処理を効率的に実装する例を示します。

public class ApiResponseHandler
{
    public record ApiResponse<T>(
        int StatusCode,
        T? Data,
        string? Message,
        IReadOnlyList<string>? Errors);

    /// <summary>
    /// HTTPステータスコードに基づいて適切なAPIレスポンスを生成します
    /// </summary>
    public ApiResponse<T> HandleResponse<T>(
        HttpStatusCode statusCode,
        T? data = default,
        string? message = null,
        IEnumerable<string>? errors = null)
    {
        return ((int)statusCode) switch
        {
            200 => new(200, data, "リクエストが成功しました", null),
            201 => new(201, data, "リソースが作成されました", null),
            204 => new(204, default, "処理は成功しましたが、返却するデータはありません", null),
            400 => new(400, default, message ?? "不正なリクエストです", errors?.ToList()),
            401 => new(401, default, message ?? "認証が必要です", errors?.ToList()),
            403 => new(403, default, message ?? "アクセスが拒否されました", errors?.ToList()),
            404 => new(404, default, message ?? "リソースが見つかりません", errors?.ToList()),
            500 => new(500, default, message ?? "サーバーエラーが発生しました", errors?.ToList()),
            _ => new((int)statusCode, default, message ?? "予期しないエラーが発生しました", errors?.ToList())
        };
    }
}

// 使用例
public class UserController
{
    private readonly ApiResponseHandler _responseHandler = new();

    public async Task<ApiResponse<UserDto>> GetUserAsync(int userId)
    {
        try
        {
            var user = await FindUserAsync(userId);
            return user != null
                ? _responseHandler.HandleResponse(HttpStatusCode.OK, user)
                : _responseHandler.HandleResponse<UserDto>(
                    HttpStatusCode.NotFound,
                    message: $"ID: {userId} のユーザーが見つかりません");
        }
        catch (Exception ex)
        {
            return _responseHandler.HandleResponse<UserDto>(
                HttpStatusCode.InternalServerError,
                message: "ユーザー情報の取得中にエラーが発生しました",
                errors: new[] { ex.Message });
        }
    }

    private Task<UserDto?> FindUserAsync(int userId)
    {
        // データベースアクセスのシミュレーション
        return Task.FromResult<UserDto?>(new UserDto(userId, "テストユーザー"));
    }
}

ワークフロー状態管理システム

複雑なビジネスワークフローの状態を管理する例を示します。

public class WorkflowManager
{
    public record WorkflowState(
        string CurrentState,
        Dictionary<string, object> Data,
        DateTime LastUpdated);

    public record WorkflowResult(
        bool Success,
        string NewState,
        string Message,
        Dictionary<string, object> UpdatedData);

    private readonly Dictionary<string, HashSet<string>> _allowedTransitions = new()
    {
        ["draft"] = new() { "review", "canceled" },
        ["review"] = new() { "approved", "rejected", "canceled" },
        ["approved"] = new() { "published", "archived" },
        ["rejected"] = new() { "draft", "canceled" },
        ["published"] = new() { "archived" },
        ["archived"] = new() { "draft" },
        ["canceled"] = new() { "draft" }
    };

    /// <summary>
    /// ワークフローの状態遷移を処理します
    /// </summary>
    public WorkflowResult ProcessTransition(
        WorkflowState currentState,
        string newState,
        Dictionary<string, object>? additionalData = null)
    {
        // 状態遷移の検証
        if (!IsValidTransition(currentState.CurrentState, newState))
        {
            return new WorkflowResult(
                false,
                currentState.CurrentState,
                $"無効な状態遷移です: {currentState.CurrentState} -> {newState}",
                currentState.Data);
        }

        // 状態に応じた処理の実行
        var result = newState switch
        {
            "review" => ValidateForReview(currentState),
            "approved" => ValidateForApproval(currentState),
            "published" => PrepareForPublishing(currentState),
            "archived" => PrepareForArchiving(currentState),
            _ => new WorkflowResult(
                true,
                newState,
                $"状態を {newState} に更新しました",
                currentState.Data)
        };

        // 追加データの統合
        if (result.Success && additionalData != null)
        {
            foreach (var (key, value) in additionalData)
            {
                result.UpdatedData[key] = value;
            }
        }

        return result;
    }

    private bool IsValidTransition(string currentState, string newState)
    {
        return _allowedTransitions.TryGetValue(currentState, out var allowedStates) &&
               allowedStates.Contains(newState);
    }

    private WorkflowResult ValidateForReview(WorkflowState state)
    {
        var updatedData = new Dictionary<string, object>(state.Data);

        if (!state.Data.ContainsKey("title") || string.IsNullOrEmpty(state.Data["title"]?.ToString()))
        {
            return new WorkflowResult(
                false,
                state.CurrentState,
                "タイトルは必須です",
                updatedData);
        }

        updatedData["reviewStartDate"] = DateTime.Now;
        return new WorkflowResult(
            true,
            "review",
            "レビュー開始",
            updatedData);
    }

    private WorkflowResult ValidateForApproval(WorkflowState state)
    {
        var updatedData = new Dictionary<string, object>(state.Data);

        if (!state.Data.ContainsKey("reviewerId"))
        {
            return new WorkflowResult(
                false,
                state.CurrentState,
                "レビュアーの指定が必要です",
                updatedData);
        }

        updatedData["approvalDate"] = DateTime.Now;
        return new WorkflowResult(
            true,
            "approved",
            "承認完了",
            updatedData);
    }

    private WorkflowResult PrepareForPublishing(WorkflowState state)
    {
        var updatedData = new Dictionary<string, object>(state.Data)
        {
            ["publishDate"] = DateTime.Now,
            ["version"] = state.Data.TryGetValue("version", out var ver)
                ? Convert.ToInt32(ver) + 1
                : 1
        };

        return new WorkflowResult(
            true,
            "published",
            "公開準備完了",
            updatedData);
    }

    private WorkflowResult PrepareForArchiving(WorkflowState state)
    {
        var updatedData = new Dictionary<string, object>(state.Data)
        {
            ["archiveDate"] = DateTime.Now,
            ["archivedBy"] = state.Data.GetValueOrDefault("currentUser", "system")
        };

        return new WorkflowResult(
            true,
            "archived",
            "アーカイブ完了",
            updatedData);
    }
}

// 使用例
public class DocumentWorkflowExample
{
    private readonly WorkflowManager _workflowManager = new();

    public async Task<WorkflowResult> ProcessDocument(string documentId, string action)
    {
        // 現在の状態を取得
        var currentState = await GetDocumentState(documentId);

        // アクションに基づいて新しい状態を決定
        var newState = action switch
        {
            "submit" => "review",
            "approve" => "approved",
            "reject" => "rejected",
            "publish" => "published",
            "archive" => "archived",
            "cancel" => "canceled",
            _ => throw new ArgumentException($"未定義のアクション: {action}")
        };

        // 状態遷移を処理
        var result = _workflowManager.ProcessTransition(
            currentState,
            newState,
            new Dictionary<string, object>
            {
                ["lastAction"] = action,
                ["lastActionDate"] = DateTime.Now
            });

        if (result.Success)
        {
            await SaveDocumentState(documentId, result);
        }

        return result;
    }

    private Task<WorkflowState> GetDocumentState(string documentId)
    {
        // データベースからドキュメントの状態を取得する処理
        return Task.FromResult(new WorkflowState(
            "draft",
            new Dictionary<string, object>
            {
                ["title"] = "サンプルドキュメント",
                ["documentId"] = documentId
            },
            DateTime.Now));
    }

    private Task SaveDocumentState(string documentId, WorkflowResult result)
    {
        // データベースにドキュメントの新しい状態を保存する処理
        return Task.CompletedTask;
    }
}

これらの実装例は、実務で直面する典型的な課題に対する解決策を示しています。

特に注目すべき点
  1. エラーハンドリング
    • 適切な例外処理
    • 明確なエラーメッセージ
    • 状態の一貫性維持
  2. 拡張性
    • 新しい状態や遷移の追加が容易
    • ビジネスルールの変更に対応しやすい構造
    • モジュール化された設計
  3. 保守性
    • 明確な責任分離
    • 自己文書化コード
    • テスト容易性

これらの例を参考に、実際のプロジェクトでswitch式を効果的に活用することができます。

switch-caseのまとめ

C#のswitch-case文は、単純な条件分岐から複雑なビジネスロジックの実装まで、幅広いシーンで活用できる強力な機能です。
モダンな機能を活用することで、より簡潔で保守性の高いコードを実現できます。パフォーマンスと保守性のバランスを考慮しながら、適切な実装パターンを選択することが重要です。

主なポイント
  1. 実装の選択
    • 単純な条件分岐には新しいswitch式を活用
    • 複雑な条件にはパターンマッチングを使用
    • 頻繁なルックアップにはDictionaryの使用を検討
  2. パフォーマンス最適化
    • 文字列比較は最小限に抑える
    • 頻出パターンを優先的に配置
    • 適切なデータ構造の選択
  3. コード品質
    • 明確な責任分離
    • 適切な例外処理の実装
    • ドキュメントコメントの付与
  4. 保守性の考慮
    • 拡張しやすい構造設計
    • 状態遷移の明確な定義
    • テスト容易性の確保