Использование записей С # 9.0 для создания структуры данных, подобной умному перечислению / дискриминированному объединению / типу суммы?

Играя с recordтипа на C#, похоже, было бы весьма полезно создавать структуры данных, подобные размеченному объединению, и мне просто интересно, не упускаю ли я некоторых ошибок, о которых я позже пожалею. Например:

abstract record CardType{
    // Case types
    public record MaleCardType(int age) : CardType{}
    public record FemaleCardType : CardType{}

    // Api
    public static MaleCardType Male(int age) => new MaleCardType(age);
    public static FemaleCardType Female => new FemaleCardType();
}

var w = CardType.Male(42);
var x = CardType.Male(42);
var y = CardType.Male(43);
var z = CardType.Female;
Assert.Equal<CardType>(w,x); //true
Assert.Equal<CardType>(x,y); //false
Assert.Equal<CardType>(y,z); //false

Кажется, это намного проще, чем создание абстрактных классов с синглетонами, компараторами равенства и всем остальным, но я упускаю какую-то причину, по которой я не хотел бы это делать?

2 ответа

Это отличный способ, я играл с ним, например, на https://fsharpforfunandprofit.com/posts/designing-for-correctness/, где есть несколько примеров кода C#, код F#, который использует типы и размеченные объединения, а затем некоторые модифицированные (но все еще ужасный код C#). Поэтому я переписываю C#, используя записи C# 9 и тот же способ выполнения DU.

Пример кода, который немного уродливее, чем F#, но все же довольно лаконичен и имеет преимущества кода F#.

      using System;
using System.Collections.Immutable;

namespace ConsoleDU
{
    record CartItem(string Value);

    record Payment(decimal Amount);

    abstract record Cart
    {
        public record Empty () : Cart
        {
            public new static Active Add(CartItem item) => new(ImmutableList.Create(item));
        }
        public record Active (ImmutableList<CartItem> UnpaidItems) : Cart
        {
            public new Active Add(CartItem item) => this with {UnpaidItems = UnpaidItems.Add(item)};
            public new Cart Remove(CartItem item) => this with {UnpaidItems = UnpaidItems.Remove(item)} switch
            {
                var (items) when items.IsEmpty => new Empty(),
                { } active => active
            };

            public new Cart Pay(decimal amount) => new PaidFor(UnpaidItems, new(amount));
        }
        public record PaidFor (ImmutableList<CartItem> PaidItems, Payment Payment) : Cart;

        public Cart Display()
        {
            Console.WriteLine(this switch
            {
                Empty => "Cart is Empty",
                Active cart => $"Cart has {cart.UnpaidItems.Count} items",
                PaidFor(var items, var payment) => $"Cart has {items.Count} paid items. Amount paid: {payment.Amount}",
                _ => "Unknown"
            });
            return this;
        }

        public Cart Add(CartItem item) => this switch
        {
            Empty => Empty.Add(item),
            Active state => state.Add(item),
            _ => this
        };

        public static Cart NewCart => new Empty();

        public Cart Remove(CartItem item) => this switch
        {
            Active state => state.Remove(item),
            _ => this
        };

        public Cart Pay(decimal amount) => this switch
        {
            Active cart => cart.Pay(amount),
            _ => this
        };
    }

    class Program
    {
        static void Main(string[] args)
        {
            Cart.NewCart
                .Display()
                .Add(new("apple"))
                .Add(new("orange"))
                .Display()
                .Remove(new("orange"))
                .Display()
                .Remove(new("apple"))
                .Display()
                .Add(new("orange"))
                .Pay(23M)
                .Display();
            ;
        }
    }
}
      record PaymentType{
    public record CreditCard(CardNumber CardNumber, SecurityCode CVV, Expiration ExpirationDate, NameOnCard Name) : PaymentType();
    public record ACH(AccountNumber AccountNumber, RoutingNumber RoutingNumber) : PaymentType();
    public record Paypal(IntentToken Token) : PaymentType();

    private PaymentType(){} // private constructor can prevent derived cases from being defined elsewhere
}

public void HandlePayment(PaymentType paymentInfo){
    paymentInfo switch {
        CreditCard cardInfo => //...
        ACH checkInfo => //...
        Paypal paypalInfo => //...
    };
}

Ссылка на исходную статью

Другие вопросы по тегам