はじめに
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; } } }
- パフォーマンス最適化
- 頻繁に使用される値はキャッシュする
- 文字列操作は最小限に抑える
- 適切なデータ構造を選択する
- メモリ効率
- 値型を適切に活用する
- 文字列プールを利用する
- 不要なオブジェクト生成を避ける
- スレッドセーフティ
- 適切な同期機構を使用する
- 状態遷移を厳密に管理する
- 競合状態を考慮した設計を行う
これらの原則に従うことで、高性能で信頼性の高い実装を実現できます。
実践的なユースケース集
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; } }
これらの実装例は、実務で直面する典型的な課題に対する解決策を示しています。
- エラーハンドリング
- 適切な例外処理
- 明確なエラーメッセージ
- 状態の一貫性維持
- 拡張性
- 新しい状態や遷移の追加が容易
- ビジネスルールの変更に対応しやすい構造
- モジュール化された設計
- 保守性
- 明確な責任分離
- 自己文書化コード
- テスト容易性
これらの例を参考に、実際のプロジェクトでswitch式を効果的に活用することができます。
switch-caseのまとめ
C#のswitch-case文は、単純な条件分岐から複雑なビジネスロジックの実装まで、幅広いシーンで活用できる強力な機能です。
モダンな機能を活用することで、より簡潔で保守性の高いコードを実現できます。パフォーマンスと保守性のバランスを考慮しながら、適切な実装パターンを選択することが重要です。
- 実装の選択
- 単純な条件分岐には新しいswitch式を活用
- 複雑な条件にはパターンマッチングを使用
- 頻繁なルックアップにはDictionaryの使用を検討
- パフォーマンス最適化
- 文字列比較は最小限に抑える
- 頻出パターンを優先的に配置
- 適切なデータ構造の選択
- コード品質
- 明確な責任分離
- 適切な例外処理の実装
- ドキュメントコメントの付与
- 保守性の考慮
- 拡張しやすい構造設計
- 状態遷移の明確な定義
- テスト容易性の確保