我正在使用 C# 访问 F# 可区分的联合,并尝试在联合的情况下使用 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 一个类型单位的字段来规避这一点,但它不是很优雅。为什么只为非空的区分联合值创建类型,最好的解决方法是什么?
我不认为猜测为什么 F# DU 以语言规范第8.5.4 部分从规定的其他 CLI 语言使用的联合类型的编译形式来实现在使用 C# 中的 F# DU 时非常重要。
对于此类互操作方案,一个好的设计是避免使用"原始"DU,而是将此实现详细信息隐藏在 F# 将向其他 CLI 语言公开的某些接口后面。
在少数情况下(例如,这个和那个(,SO涵盖了使用C#中的F# DU的问题,并就如何以正确的方式进行操作给出了建议。
但是,如果您坚持使用依赖于F# DU 实现细节的 C# 错误方式,则以下 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
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# 类。案例陈述的第一部分将始终计算为 true,因为字母的类型为 Letter。when 子句指定字母必须等于对应于 C 和 D 的空区分联合值的 Letter 的单例实例。
如果你不介意向类型添加一点复杂性,可以像这样定义 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;
}