What are the best ways to use F# Discriminated Unions from C#?
I have been digging into this problem for a while, I have probably found the simplest way, but as it is rather complex, there may be some other thing I don't see...
Having a discriminated union, e.g.:
type Shape =
| Rectangle of float * float
| Circle of float
the usage from C# I found would be (avoiding using vars, to make the type obvious):
Shape circle = Shape.NewCircle(5.0);
if (circle.IsCircle)
{
Shape.Circle c = (Shape.Circle)circle;
double radius = c.Item;
}
In C#, the NewXXXX
static methods always create object of the Shape
class, there is also a method IsXXXX
to check if the object is of the type; if and only if yes, it is castable to the Shape.XXXX
class, and only then its items are accessible; constructor of the Shape.XXXX
classes are internal, i.e. unaccessible.
Is anyone aware of a simpler option to get the data from a discriminated union?
If you are writing a library in F# that is exposed to C# developers, then C# developers should be able to use it without knowing anything about F# (and without knowing that it was written in F#). This is also recommended by F# design guidelines.
For discriminated unions, this is tricky, because they follow different design principles than C#. So, I would probably hide all processing functionality (like calculating area) in the F# code and expose it as ordinary members.
If you really need to expose the two cases to C# developers, then I think something like this is a decent option for a simple discriminated union:
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
In C#, you can use it in the same way as the familiar TryParse
methods:
int w, h, r;
if (shape.TryRectangle(out w, out h)) {
// Code for rectangle
} else if (shape.TryCircle(out r)) {
// Code for circle
}
According to the F# spec, the only interop available is through the following instance methods
.IsC...
.Tag
(which gives an integer tag to each case)
.Item
(on the subtypes to get the data - this is only present when there is more than one union case)However, you are free to write methods in the F# to make the interop easier.
Assuming that we need to calculate the area of each Shape polymorphically.
In C# we would normally create a hypothetical object hierarchy and a Visitor. In this example, we would have to create a ShapeVisitor class and then a derived ShapeAreaCalculator visitor class.
In F#, we can use Pattern Matching on the Shape
type:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With