C# Academy · レッスン

判別共用体(record)によるドメインモデリング — C# 6でのエミュレーション

タグ用のenumと小さなクラスを使ってC# 6で判別共用体をモデル化し、ファクトリーメソッドを作り、defaultで例外をスローするswitchで各バリアントを処理します。

レッスン 3/38 ステップ

「判別共用体(record)によるドメインモデリング — C# 6でのエミュレーション」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン3/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全3レッスンが含まれています。

C# 6でのDUの考え方

目的: C# 6で小さな判別共用体を構築します。

  • アクティブなケースを示すタグ enum
  • データを保持する小さなケース用クラス
  • 有効なケースを作成するファクトリメソッド
  • すべてのバリアントを処理するswitch+default throw

タグ+ファクトリ

タグ enumとファクトリメソッドを使って有効なケースだけを構築します。他のケースのフィールドは未使用のままにします。

using System;

public enum PaymentKind { Cash, Card, Wire }

public sealed class Payment
{
  public PaymentKind Kind;

  // per-case data (only one is meaningful per instance)
  public string CardLast4; // for Card
  public string Iban;      // for Wire

  private Payment() { }

  // factories keep invariants
  public static Payment Cash()
  {
    Payment p = new Payment();
    p.Kind = PaymentKind.Cash;
    return p;
  }

  public static Payment Card(string last4)
  {
    if (string.IsNullOrEmpty(last4) || last4.Length != 4)
      throw new ArgumentException("last4 must be 4 digits", "last4");
    Payment p = new Payment();
    p.Kind = PaymentKind.Card;
    p.CardLast4 = last4;
    return p;
  }

  public static Payment Wire(string iban)
  {
    if (string.IsNullOrEmpty(iban))
      throw new ArgumentException("iban must not be empty", "iban");
    Payment p = new Payment();
    p.Kind = PaymentKind.Wire;
    p.Iban = iban;
    return p;
  }
}

public class Program
{
  public static void Main(string[] args)
  {
    Payment a = Payment.Cash();
    Payment b = Payment.Card("1234");
    Payment c = Payment.Wire("TR00BANKIBAN");
    Console.WriteLine(a.Kind + ", " + b.Kind + ", " + c.Kind);
  }
}

バリアントの処理

タグに対する従来のswitchによって、処理内容が明確になります。default throwにより、テスト中に未処理のバリアントを明らかにできます。

using System;

public enum PaymentKind { Cash, Card, Wire }

public sealed class Payment
{
  public PaymentKind Kind;
  public string CardLast4;
  public string Iban;

  public static Payment Cash()
  {
    Payment p = new Payment(); p.Kind = PaymentKind.Cash; return p;
  }
  public static Payment Card(string last4)
  {
    if (string.IsNullOrEmpty(last4) || last4.Length != 4) throw new ArgumentException("last4");
    Payment p = new Payment(); p.Kind = PaymentKind.Card; p.CardLast4 = last4; return p;
  }
  public static Payment Wire(string iban)
  {
    if (string.IsNullOrEmpty(iban)) throw new ArgumentException("iban");
    Payment p = new Payment(); p.Kind = PaymentKind.Wire; p.Iban = iban; return p;
  }
}

public static class Exhaustive
{
  public static Exception Unhandled(object x)
  {
    return new InvalidOperationException("Unhandled case: " + x);
  }
}

public class Program
{
  static string Describe(Payment p)
  {
    switch (p.Kind)
    {
      case PaymentKind.Cash: return "Cash";
      case PaymentKind.Card: return "Card ****" + p.CardLast4;
      case PaymentKind.Wire: return "Wire " + p.Iban;
      default: throw Exhaustive.Unhandled(p.Kind);
    }
  }

  public static void Main(string[] args)
  {
    Console.WriteLine(Describe(Payment.Card("9876")));
  }
}

Matchヘルパー(visitor風)

小さなMatchヘルパーにswitchを集約して値を返すことで、呼び出し側をすっきり保てます。

using System;

public enum PaymentKind { Cash, Card, Wire }

public sealed class Payment
{
  public PaymentKind Kind;
  public string CardLast4;
  public string Iban;

  public static Payment Cash() { Payment p = new Payment(); p.Kind = PaymentKind.Cash; return p; }
  public static Payment Card(string last4) { Payment p = new Payment(); p.Kind = PaymentKind.Card; p.CardLast4 = last4; return p; }
  public static Payment Wire(string iban) { Payment p = new Payment(); p.Kind = PaymentKind.Wire; p.Iban = iban; return p; }
}

public static class PaymentMatch
{
  public static T Match<T>(
    Payment p,
    Func<T> onCash,
    Func<string, T> onCard,
    Func<string, T> onWire)
  {
    switch (p.Kind)
    {
      case PaymentKind.Cash: return onCash();
      case PaymentKind.Card: return onCard(p.CardLast4);
      case PaymentKind.Wire: return onWire(p.Iban);
      default: throw new InvalidOperationException("Unhandled: " + p.Kind);
    }
  }
}

public class Program
{
  public static void Main(string[] args)
  {
    Payment p = Payment.Card("1234");
    string summary = PaymentMatch.Match(
      p,
      delegate { return "Cash"; },
      delegate(string last4) { return "Card ****" + last4; },
      delegate(string iban) { return "Wire " + iban; }
    );
    Console.WriteLine(summary);
  }
}

ファクトリによる不変条件

ファクトリによって不変条件を保証します。各ケースは意味のあるフィールドだけを初期化し、無効な入力は早期に拒否します。

using System;

public enum ResultKind { Ok, Error }

public sealed class Result
{
  public ResultKind Kind;
  public string Message; // only for Error
  public int Value;      // only for Ok

  private Result() { }

  public static Result Ok(int value)
  {
    Result r = new Result();
    r.Kind = ResultKind.Ok;
    r.Value = value;
    r.Message = null;
    return r;
  }

  public static Result Error(string message)
  {
    if (string.IsNullOrEmpty(message)) throw new ArgumentException("message");
    Result r = new Result();
    r.Kind = ResultKind.Error;
    r.Message = message;
    r.Value = 0;
    return r;
  }
}

public class Program
{
  static string Show(Result r)
  {
    switch (r.Kind)
    {
      case ResultKind.Ok: return "OK: " + r.Value;
      case ResultKind.Error: return "ERROR: " + r.Message;
      default: throw new InvalidOperationException("Unhandled: " + r.Kind);
    }
  }

  public static void Main(string[] args)
  {
    Console.WriteLine(Show(Result.Ok(5)));
    Console.WriteLine(Show(Result.Error("Boom")));
  }
}

ヒントとトレードオフ

ヒント:

  • 小さく、目的を絞ったケースとファクトリを優先します。
  • コードをDRYに保つため、バリアントの処理を1か所(switchヘルパー)に集約します。
  • テストで未処理のケースを明らかにするため、default throwを維持します。

トレードオフ: 最新のrecordよりボイラープレートは増えますが、明確でC# 6に対応できます。

DUを再現するアプローチ

確認問題: C# 6で判別共用体を再現する簡単な方法は何ですか?

振り返り

振り返り: タグ enumでバリアントをモデル化し、ファクトリで有効なケースを構築し、処理をswitch(defaultで例外をスロー)に集約して実行時の網羅性を確保します。

無料で開始

AI チューターと学ぶ C# — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
93
レッスン
346

よくある質問

「判別共用体(record)によるドメインモデリング — C# 6でのエミュレーション」レッスンは無料ですか?

はい。「判別共用体(record)によるドメインモデリング — C# 6でのエミュレーション」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全3レッスンが含まれています。

「判別共用体(record)によるドメインモデリング — C# 6でのエミュレーション」で何を学びますか?

タグ用のenumと小さなクラスを使ってC# 6で判別共用体をモデル化し、ファクトリーメソッドを作り、defaultで例外をスローするswitchで各バリアントを処理します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C# Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/3です。

「判別共用体(record)によるドメインモデリング — C# 6でのエミュレーション」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC# Academyレッスンでコードを書いて実行できますか?

はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. isパターン、関係パターンと論理パターン(C# 6でのエミュレーション)
  2. switch式とwhenによる網羅性チェック(C# 6でのエミュレーション)
  3. 判別共用体(record)によるドメインモデリング — C# 6でのエミュレーション
← C# Academyに戻る