一般社団法人 全国個人事業主支援協会

COLUMN コラム

C# 14 (.NET 10) で導入された「判別共用体(Discriminated Unions)」およびその他の新機能について解説します。
本記事では、判別共用体の概要とドメイン駆動設計(DDD)への活用法、そしてC# 14でのその他の追加機能を取り上げます。

判別共用体とは

判別共用体とは、「いくつかの異なる型のうち、いずれか1つの値を持つことができる型」を表現する仕組みです。F#などの関数型言語で古くから利用されている機能です。

F#での例

支払い方法を定義する場合、F#では以下のように記述します。

type PaymentMethod =
    | Cash
    | CreditCard of cardNumber: string
    | BankTransfer of accountId: int

各状態に異なるデータ(クレジットカード番号や口座IDなど)を持たせることができる点が特徴です。

C# 14での記述例

C# 14の判別共用体では、これを以下のように記述できるようになりました。

union PaymentMethod
{
    Cash,
    CreditCard(string CardNumber),
    BankTransfer(int AccountId)
}

ドメイン駆動設計(DDD)への活用

判別共用体は、ドメイン駆動設計(DDD)において「不正な状態をコンパイルレベルで防ぐ(Make Illegal States Unrepresentable)」目的に適しています。ユーザーの「登録結果」を表現する場合の比較は以下の通りです。

従来のアプローチ

public class RegistrationResult
{
    public bool IsSuccess { get; set; }
    public DateTime? RegisteredAt { get; set; }
    public string? ErrorMessage { get; set; }
}

この設計では、「IsSuccessがtrueであるにも関わらずErrorMessageが設定されている」といった矛盾した状態を作成できます。

判別共用体を活用したアプローチ

union RegistrationResult
{
    Success(DateTime RegisteredAt),
    Failure(string ErrorMessage)
}

判別共用体を使用することで、成功時と失敗時の状態を明確に分離し、関数型言語における「Either モナド」のような直感的なエラーハンドリングが可能になります。これにより矛盾したデータ構造の発生を防ぐことができます。

switch式による網羅性チェック

switch式と組み合わせることで、型の網羅性チェックが可能になります。

string message = result switch
{
    RegistrationResult.Success s => $"登録完了: {s.RegisteredAt}",
    RegistrationResult.Failure f => $"エラー: {f.ErrorMessage}",
};

将来的に状態が追加された場合、switch式に分岐の記述漏れがあるとコンパイル時に警告またはエラーが発生するため、保守性が向上します。

その他のC# 14 新機能

判別共用体の他にも、C# 14ではいくつかの新機能が導入されています。公式ドキュメントにある機能について、独自のサンプルコードで挙動を補足します。

拡張プロパティ(拡張メンバー)

従来の拡張メソッドに加え、プロパティ等の多様なメンバーを拡張可能になります。論理的なまとまりをextensionブロックで定義します。

public static class StringExtensions
{
    // C# 14: extension機能
    extension (string s)
    {
        // 拡張プロパティの定義
        public bool IsNullOrWhiteSpace => string.IsNullOrWhiteSpace(s);
    }
}

field キーワード

プロパティの自動実装(バッキングフィールド)に対して、明示的な変数宣言なしにfieldキーワードを用いてアクセスできるようになります。

public class UserProfile
{
    // fieldキーワードを用いたプロパティ
    public string DisplayName { get; set => field = value.Trim(); }
}

nameofでのバインドされていないジェネリック型の指定

nameof演算子に、型引数を指定しないジェネリック型をそのまま渡すことが可能になりました。

// 以前は nameof(Dictionary) 等とする必要があった
// C# 14では "Dictionary" という文字列を直接取得できる
var typeName = nameof(Dictionary);

この記事をシェアする

  • Twitterでシェア
  • Facebookでシェア
  • LINEでシェア