はじめに
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の使用を検討
- パフォーマンス最適化
- 文字列比較は最小限に抑える
- 頻出パターンを優先的に配置
- 適切なデータ構造の選択
- コード品質
- 明確な責任分離
- 適切な例外処理の実装
- ドキュメントコメントの付与
- 保守性の考慮
- 拡張しやすい構造設計
- 状態遷移の明確な定義
- テスト容易性の確保

