F # Дискриминация использования Союза с С#

Каковы наилучшие способы использования F # Discriminated Unions из С#?

Я некоторое время копался в этой проблеме, я, вероятно, нашел самый простой способ, но, поскольку он довольно сложный, может быть и другое, чего я не вижу...

Наличие дискриминационного объединения, например:

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

использование С#, которое я нашел, будет (избегая использования vars, чтобы сделать тип очевидным):

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

В С# статические методы NewXXXX всегда создают объект класса Shape, также существует метод IsXXXX для проверки того, является ли объект типом; если и только если "да", то он поддается классу Shape.XXXX, и только тогда его элементы доступны; конструктор классов Shape.XXXX является внутренним, то есть недоступным.

Кто-нибудь знает о более простой опции для получения данных от дискриминационного объединения?

Ответы

Ответ 1

Если вы пишете библиотеку в F #, которая предоставляется разработчикам С#, разработчики С# должны иметь возможность использовать ее, не зная ничего о F # (и не зная, что она написана в F #). Это также рекомендуется Руководством по дизайну F #.

Для дискриминационных союзов это сложно, потому что они следуют различным принципам дизайна, чем С#. Поэтому я, вероятно, скрою всю функциональность обработки (например, вычислительную область) в коде F # и выставляю ее как обычные члены.

Если вам действительно нужно разоблачить два случая для разработчиков С#, то я думаю, что что-то вроде этого является достойным вариантом для простого дискриминационного объединения:

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

В С# вы можете использовать его так же, как знакомые методы TryParse:

int w, h, r;
if (shape.TryRectangle(out w, out h)) { 
  // Code for rectangle
} else if (shape.TryCircle(out r)) {
  // Code for circle
}

Ответ 2

В соответствии с спецификацией F # единственный доступный доступ к нему осуществляется через следующие методы экземпляра

  • .IsC...

  • .Tag (который дает целочисленный тег для каждого случая)

  • .Item (о подтипах для получения данных - это присутствует только при наличии более одного случая объединения)

Тем не менее, вы можете свободно писать методы в F #, чтобы упростить взаимодействие.

Ответ 3

Предполагая, что нам нужно вычислить площадь каждой фигуры полиморфно.

В С# мы обычно создаем гипотетическую иерархию объектов и Visitor. В этом примере нам нужно будет создать класс ShapeVisitor, а затем производный класс посетителей ShapeAreaCalculator.

В F # мы можем использовать Match Matching для типа 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