C#类型用于空的F#判别式联合案例

3

我正在使用C#访问一个F#的判定联合(discriminated union),并尝试使用switch语句处理该联合的情况。这对于至少有一个字段的值很好用,但是对于空值则不行,因为这些空值没有相应的类生成,只有一个属性。考虑以下的F# 判定联合:

type Letter = A of value:int | B of value:string | C | D

在C#中,我有以下带有参数Letter的函数内部的switch语句:
switch (letter)
{
    case A a: Console.WriteLine(a.value); break;
    case B b: Console.WriteLine(b.value); break;
    default:
        if (letter.IsC) Console.WriteLine("C");
        else if (letter.IsD) Console.WriteLine("D");
}

默认情况下处理联合值为空的情况。我更喜欢:

switch (letter)
{
    case A a: Console.WriteLine(a.value); break;
    case B b: Console.WriteLine(b.value); break;
    case C c: Console.WriteLine("C"); break;
    case D d: Console.WriteLine("D"); break;
}

但是这样做不起作用,因为类型名称 C 和 D 不存在 - C 和 D 是属性而不是类型。我可以通过给 C 和 D 赋一个 unit 类型的字段来绕过这个问题,但这并不太优雅。为什么类型只能为非空辨别联合值创建,并且最佳解决方法是什么?

2
你尝试过将 F# 编译成程序集,然后再反编译成 C# 吗? - Flydog57
1
谢谢您的建议。我已经查看了这里将F#反编译为C#(https://fsharpforfunandprofit.com/posts/fsharp-decompiled/),这导致了我下面给出的答案。 - Mike
3个回答

2

我认为,在使用F# DUs时,猜测为什么要按照语言规范部分8.5.4中所述的方式实现并不是非常重要。

对于这种互操作场景,一个好的设计应该避免使用“原始”DU,而是将此实现细节隐藏在F#向其他CLI语言公开的某个接口后面。

在一些场合(例如这一个那一个),关于如何从C#中使用F# DU的问题已经在SO上得到了讨论,并给出了正确的做法建议。

但是,如果你坚持用C#依赖F# DU的特定实现细节,以下C# hack可以解决问题:

namespace ConsoleApp1
{
    class Program {

        private static void unwindDU(Letter l)
        {
            switch (l.Tag)
            {
                case Letter.Tags.A: Console.WriteLine(((Letter.A)l).value); break;
                case Letter.Tags.B: Console.WriteLine(((Letter.B)l).value); break;
                case Letter.Tags.C: Console.WriteLine("C"); break;
                case Letter.Tags.D: Console.WriteLine("D"); break;
            }
        }

        static void Main(string[] args)
        {
            unwindDU(Letter.NewA(1));
            unwindDU(Letter.C);
        }
    }
}

执行后会返回

1
C

2
switch (letter)
{
    case A a: Console.WriteLine(a.value); break;
    case B b: Console.WriteLine(b.value); break;
    case Letter l when l == C: Console.WriteLine("C"); break;
    case Letter l when l == D: Console.WriteLine("D"); break;
}

空的辨别联合使用单例模式,通过构造函数传递标记,使属性C分配给新的Letter(0),D分配给新的Letter(1),其中Letter是相应的C#类。 case语句的第一部分将始终计算为true,因为letter是Letter类型。 when子句指定该字母必须等于对应于C和D的空辨别联合值的单例实例的Letter。


1
我在这里写了几个选项: https://stuartlang.uk/pattern-matching-fsharp-union-types-in-csharp/ 如果有新的选项出现,我会添加更多内容。 - Stuart

2

如果您不介意在类型中添加一点复杂性,可以像下面这样定义您的F#类型:

type Letter = A of value:int | B of value:string | C of unit | D of unit

完成上述步骤后,您可以按照以下方式在C#中进行模式匹配:

switch (letter)
{
    case A a: Console.WriteLine("A"); break;
    case B b: Console.WriteLine("B"); break;
    case C _: Console.WriteLine("C"); break;
    case D _: Console.WriteLine("D"); break;
}

2
这个可能性在问题中已经提到了。 - Bent Tranberg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接