从C#使用F#判别联合类型

20

如何在C#中最好地使用F#判别联合?

我已经研究了这个问题一段时间了,可能找到了最简单的方法,但由于它相当复杂,可能还有其他我没有看到的东西...

拥有一个判别联合,例如:

type Shape =
    | Rectangle of float * float
    | Circle of float

我发现在C#中使用的方式是(避免使用vars,以使类型明显):

Shape circle = Shape.NewCircle(5.0);
if (circle.IsCircle)
{
    Shape.Circle c = (Shape.Circle)circle;
    double radius = c.Item;
}

在C#中,NewXXXX 静态方法总是创建 Shape 类的对象,还有一个方法 IsXXXX 用于检查对象是否为该类型;只有在是的情况下,它才能强制转换为 Shape.XXXX 类,并且只有这样才能访问其项。 Shape.XXXX 类的构造函数是内部的,即无法访问。

是否有人知道从带判别联合的数据中获取数据的更简单的选项?


你是否必须使用判别联合(discriminated union)?我所看到的所有F#/C#互操作代码库都会在这种情况下暴露出类型化工厂,以使其更加美观。据我所知,没有更简单的方法。 - Simon Whitehead
2
重复什么是在C#中访问F#区分联合类型数据的最简单方法?以及https://dev59.com/6FTTa4cB1Zd3GeqPt5g1 - Mauricio Scheffer
具体示例:https://github.com/mausch/EdmundsNet/blob/b5ca7a7d7a883f4f0b2f7f7a7af032534e792cdb/EdmundsNet/Vehicles.fs#L119-L129 - Mauricio Scheffer
现在我会继续使用C#中的dynamicdynamic circle = ...; double radius = circle.Item; - nawfal
@nawfal 在这里使用 dynamic 将阻止您对类型进行静态分析(区分联合的主要优点之一)。 - Mauricio Scheffer
@MauricioScheffer 是的。鉴于今天的限制,这是我建议的一个简单的替代方案。 - nawfal
3个回答

18
如果你正在使用F#编写一个库,供C#开发人员使用,则C#开发人员应该能够在不知道任何关于F#的情况下使用它(并且不知道它是用F#编写的)。这也是F#设计指南所推荐的。
对于区分联合类型,这很棘手,因为它们遵循与C#不同的设计原则。因此,我可能会将所有处理功能(例如计算面积)隐藏在F#代码中,并将其公开为普通成员。
如果你确实需要向C#开发人员公开两种情况,那么我认为这样的简单区分联合类型是一个不错的选择:
type Shape =
    | Rectangle of float * float
    | Circle of float
    member x.TryRectangle(width:float byref, height:float byref) =
      match x with
      | Rectangle(w, h) -> width <- w; height <- h; true
      | _ -> false
    member x.TryCircle(radius:float byref) =
      match x with
      | Circle(r) -> radius <- r; true
      | _ -> false

在C#中,您可以像熟悉的TryParse方法一样使用它:
int w, h, r;
if (shape.TryRectangle(out w, out h)) { 
  // Code for rectangle
} else if (shape.TryCircle(out r)) {
  // Code for circle
}

3
为什么你总是推荐非穷尽匹配,明明做穷尽匹配很容易呀?请翻译。 - Mauricio Scheffer
2
主要是因为上述方法易于使用,并遵循C#中人们已经熟悉的标准编码风格(例如,使用TryParse函数非常普遍,而且不需要将所有代码放在lambda内部 - 大多数C# lambda函数的使用范围都相当小)。 - Tomas Petricek
2
你能否在这些答案中至少提到“整体性”一词,以便人们有所依据来正确评估呢?请至少考虑包含客观术语(例如整体性)以及主观/模糊的术语(例如熟悉度/惯用语)。因为我曾经因为不知道这些事情而浪费了无数个小时。我不希望其他人也经历同样的痛苦。 - Mauricio Scheffer

7
根据F#规范,唯一可用的互操作是通过以下实例方法进行的:
  • .IsC...

  • .Tag(为每个case分配一个整数标记)

  • .Item(在子类型上获取数据 - 仅当存在多个联合case时才存在)
但是,您可以在F#中编写方法以使互操作更加容易。

4
假设我们需要以多态方式计算每个形状的面积。
在C#中,我们通常会创建一个假想的对象层次结构和访问者。在本例中,我们需要创建一个ShapeVisitor类,然后是一个派生的ShapeAreaCalculator访问者类。
在F#中,我们可以对Shape类型使用模式匹配:
let rectangle = Rectangle(1.3, 10.0)
let circle = Circle (1.0)

let calculateArea shape =
    match shape with
    | Circle radius -> 3.141592654 * radius * radius
    | Rectangle (height, width) -> height * width

let rectangleArea = calculateArea(rectangle)
// -> 1.3 * 10.0

let circleArea = calculateArea(circle)
// -> 3.141592654 * 1.0 * 1.0

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