我正在使用C#访问F#区分联合并尝试在联合的情况下使用switch语句。这适用于具有至少一个字段但不包含空值的值,因为这些值没有生成相应的类,只有属性。考虑以下F#区分联合。
type Letter = A of value:int | B of value:string | C | D
在C#中,我在一个函数内部有以下switch语句,该函数具有Letter类型的参数字母:
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");
}
默认情况处理union值为空的情况。我更喜欢:
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一个类型单位的字段来规避这个,但它不是很优雅。为什么仅为非空的区分联合值创建类型,哪种方法最好?
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子句指定该字母必须等于Letter的单例实例,该实例对应于C和D的空的区别联合值。
我不认为在使用C#中的F #DU时,猜测为什么F #DU的实现方式与语言规范第8.5.4节“从其他CLI语言使用的联合类型的编译形式”一样非常重要。
这种互操作方案的一个好设计是避免使用“原始”DU,而是将这个实现细节隐藏在F#暴露给其他CLI语言的某些接口背后。
在少数情况下(例如this one和that one),使用来自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
如果您不介意为您的类型添加一点复杂性,您可以定义您的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;
}