C# Academy · Урок

Моделирование предметной области с размеченными объединениями (записи) — эмуляция в C# 6

Моделируйте размеченное объединение в C# 6 с помощью перечисления тегов и небольших классов, создавайте фабричные методы и обрабатывайте варианты через switch с выбросом исключения по умолчанию.

Урок 3 из 38 шагов

«Моделирование предметной области с размеченными объединениями (записи) — эмуляция в C# 6» — бесплатный урок C# Academy на CoddyKit. Это урок 3 из 3. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 3 уроков всего.

Идея DU в C# 6

Цель: создать небольшое объединение с различающими вариантами в C# 6.

  • Перечисление-тег для активного варианта
  • Небольшие классы вариантов для данных
  • Методы Factory для создания допустимых вариантов
  • Оператор выбора и исключение по умолчанию для обработки всех вариантов

Тег и фабрики

Используйте перечисление-тег и методы Factory, чтобы создавать только допустимые варианты; поля других вариантов остаются неиспользованными.

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);
  }
}

Обработка вариантов

Классический оператор выбора по тегу обеспечивает понятную обработку. Ветвь по умолчанию с исключением обнаруживает необработанные варианты во время тестов.

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

Небольшой вспомогательный метод Match централизует оператор выбора и возвращает значение, оставляя места вызова чистыми.

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.
  • Оставляйте ветвь по умолчанию с исключением, чтобы выявлять пропущенные варианты в тестах.

Компромиссы: шаблонного кода больше, чем у современных записей, зато решение понятно и совместимо с C# 6.

Подход к имитации DU

Быстрая проверка: как проще всего имитировать объединение с различающими вариантами в C# 6?

Повторение

Повторение: моделируйте варианты с помощью перечисления-тега, создавайте допустимые варианты через фабрики и централизуйте обработку в операторе выбора (ветвь по умолчанию выбрасывает исключение), чтобы проверять полноту обработки во время выполнения.

Можно начать бесплатно

Изучай C# с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
93
Уроки
346

Часто задаваемые вопросы

Урок «Моделирование предметной области с размеченными объединениями (записи) — эмуляция в C# 6» бесплатный?

Да — полный текст урока «Моделирование предметной области с размеченными объединениями (записи) — эмуляция в C# 6» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 3 уроков всего.

Чему я научусь в уроке «Моделирование предметной области с размеченными объединениями (записи) — эмуляция в C# 6»?

Моделируйте размеченное объединение в C# 6 с помощью перечисления тегов и небольших классов, создавайте фабричные методы и обрабатывайте варианты через switch с выбросом исключения по умолчанию. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 3.

Сколько времени занимает урок «Моделирование предметной области с размеченными объединениями (записи) — эмуляция в C# 6»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Шаблоны is, реляционные и логические шаблоны (эмуляция в C# 6)
  2. Выражения switch и исчерпывающие проверки с when (эмуляция в C# 6)
  3. Моделирование предметной области с размеченными объединениями (записи) — эмуляция в C# 6
← Назад к C# Academy