【问题标题】:Using C# 9.0 records to build smart-enum-like/discriminated-union-like/sum-type-like data structure?使用 C# 9.0 记录构建类似智能枚举/可区分联合/类似总和类型的数据结构?
【发布时间】:2020-12-22 17:32:59
【问题描述】:

在 C# 中使用 record 类型,构建类似可区分联合的数据结构似乎非常有用,我只是想知道我是否遗漏了一些我会后悔的陷阱之后。例如:

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

这似乎比使用单例和相等比较器以及所有这些构建抽象类要简单得多,但我是否错过了一些我不想这样做的原因?

【问题讨论】:

  • DU 允许您根据类型编写详尽的开关表达式。您可以简单地通过从同一个空接口继承不同类型来进行非穷举切换,不需要单例或抽象类。问题是穷举。 this 代码如何让您编写详尽的 switch 表达式?
  • 似乎您试图复制 F# 类型的构造函数。这不是让 C# DU 工作所缺少的。事实上,如果您使用布尔属性(或编译器已知值的任何类型,如果存在这种情况),您可以获得详尽的短裤匹配。 Check this answer to a similar question.
  • 谢谢@PanagiotisKanavos!我认为我的问题不在于模式匹配方面,因为无论如何这在 C# 中从来都不是一件容易的事,而是关于使用记录来完成这个数据结构而不是类。 (是的,我正在努力让 C# 像 F# 一样工作,哈哈。不幸的是,我不需要我的团队授权来推动我们使用 F#,但如果我能与 C# 足够接近,我会很高兴的! )
  • 模式匹配简单而新颖,never been easy 不适用。 DU 的问题在于广泛匹配,否则您可以执行 F# 对 C# 所做的相同操作。如果它们也那么容易,C# 团队也不会延迟它们两次。他们本可以选择 TypeScript 的功能,强迫人们打开标签,但这会使 DU 很难使用
  • 感谢@PanagiotisKanavos,感谢您的 cmets。

标签: oop enums discriminated-union c#-9.0 record-classes


【解决方案1】:

这是一个很好的方法,我一直在玩它,例如,https://fsharpforfunandprofit.com/posts/designing-for-correctness/ 有一些 C# 代码示例,一些使用类型和可区分联合的 F# 代码,然后进行了一些修改(但是仍然很糟糕的 C# 代码)。所以我用 C# 9s 记录和做 DUs 的相同方式重写了 C#

示例代码,比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();
            ;
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    • 2013-04-07
    • 2020-11-10
    • 1970-01-01
    • 1970-01-01
    • 2016-11-26
    • 1970-01-01
    相关资源
    最近更新 更多