C# Academy · Oppitunti

Toimialuemallinnus erotelluilla unioneilla (record-tyypit) — C# 6 -jäljitelmä

Mallintakaa C# 6:ssa erottelu-unioni tunniste-enumeraattorilla ja pienillä luokilla, luokaa factory-menetelmiä ja käsitelkää variantit switch-lauseella, jonka oletushaara heittää poikkeuksen.

Oppitunti 3/38 vaihetta

Toimialuemallinnus erotelluilla unioneilla (record-tyypit) — C# 6 -jäljitelmä on ilmainen C# Academy-oppitunti CoddyKitissä. Tämä on oppitunti 3/3. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu C# Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. C# Academy-kurssilla on yhteensä 3 oppituntia.

DU-ajatus C# 6:ssa

Tavoite: Rakentakaa pieni discriminated union C# 6:lla.

  • Tag-enum aktiivisen tapauksen ilmoittamista varten
  • Pienet tapausluokat tietoja varten
  • Tehdasmetodit kelvollisten tapausten luomista varten
  • Switch + default throw kaikkien muunnelmien käsittelyä varten

Tag ja tehdasmetodit

Käyttäkää tag-enumia ja tehdasmetodeja vain kelvollisten tapausten muodostamiseen; muiden tapausten kentät jäävät käyttämättä.

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

Muunnelmien käsittely

Tagiin kohdistuva perinteinen switch tekee käsittelystä selkeää. default throw paljastaa käsittelemättömät muunnelmat testien aikana.

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-apufunktio (visitor-tyylinen)

Pieni Match-apufunktio keskittää switch-rakenteen ja palauttaa arvon, jolloin kutsukohdat pysyvät selkeinä.

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

Invariantit tehdasmetodeilla

Tehdasmetodit varmistavat invariantit: kukin tapaus alustaa vain sille merkitykselliset kentät, ja virheelliset syötteet hylätään ajoissa.

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

Vinkit ja kompromissit

Vinkit:

  • Suosikaa pieniä, tarkasti rajattuja tapauksia ja tehdasmetodeja.
  • Käsitelkää muunnelmat yhdessä paikassa (switch-apufunktiossa), jotta koodi pysyy DRY-periaatteen mukaisena.
  • Pitäkää mukana default throw, jotta puuttuvat tapaukset paljastuvat testeissä.

Kompromissit: Koodia on enemmän kuin moderneissa record-tyypeissä, mutta ratkaisu on selkeä ja yhteensopiva C# 6:n kanssa.

DU:n jäljittelytapa

Pikatarkistus: mikä on yksinkertainen tapa jäljitellä discriminated unionia C# 6:ssa?

Kertaus

Kertaus: Mallintakaa muunnelmat tag-enumilla, muodostakaa kelvolliset tapaukset tehdasmetodeilla ja keskittäkää käsittely switch-rakenteeseen (default heittää poikkeuksen), jotta kaikkien tapausten kattavuus varmistuu ajonaikaisesti.

Aloita maksutta

Opi C# tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
93
Oppitunnit
346

Usein kysytyt kysymykset

Onko oppitunti ”Toimialuemallinnus erotelluilla unioneilla (record-tyypit) — C# 6 -jäljitelmä” ilmainen?

Kyllä – oppitunnin ”Toimialuemallinnus erotelluilla unioneilla (record-tyypit) — C# 6 -jäljitelmä” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko C# Academy-kurssin, päivitä CoddyKit PROhon. C# Academy-kurssilla on yhteensä 3 oppituntia.

Mitä opin oppitunnilla ”Toimialuemallinnus erotelluilla unioneilla (record-tyypit) — C# 6 -jäljitelmä”?

Mallintakaa C# 6:ssa erottelu-unioni tunniste-enumeraattorilla ja pienillä luokilla, luokaa factory-menetelmiä ja käsitelkää variantit switch-lauseella, jonka oletushaara heittää poikkeuksen. Harjoittelet C# Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni C# Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin C# Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 3/3.

Kuinka kauan ”Toimialuemallinnus erotelluilla unioneilla (record-tyypit) — C# 6 -jäljitelmä”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä C# Academy-oppitunnilla?

Kyllä. Jokainen C# Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. is-mallit sekä relaatio- ja logiikkamallit (C# 6 -jäljitelmä)
  2. switch-lausekkeet ja kattavuustarkistukset when-ehdolla (C# 6 -jäljitelmä)
  3. Toimialuemallinnus erotelluilla unioneilla (record-tyypit) — C# 6 -jäljitelmä
← Takaisin: C# Academy