Some time ago, I wrote about calling C# byref methods from F#. This time, I wanted to do things in reverse – the motivation being to provide a neat way of working with F# discriminated unions from C#.
Now, some support comes for free when you define a discriminated union in F#. For example, given this union type:
type Vehicle | Car of int // number of doors | Lorry of double // weight in tonnes
then from C#, you could query the union case in turn like this:
if (vehicle.IsCar) { var doors = ((Vehicle.Car)vehicle).Item; driveCar( doors ); } else if (vehicle.IsLorry) { var weight = ((Vehicle.Lorry)vehicle).Item; driveLorry( weight ); }
However, although the provided “Is” methods are neat, the cast and the requirement to call “.Item” are rather ugly. What you really want is the ability to get the value of the union case without casting. That’s achievable if you add these methods to the original union definition by hand:
using System.Runtime.InteropServices // for [<Out>] type Vehicle | Car of int // number of doors | Lorry of double // weight in tonnes with member this.TryGetCar( [<Out>] result : int byref ) = match this with | Car doors -> result <- doors true | _ -> false member this.TryGetLorry( [<Out>] result : double byref ) = match this with | Lorry weight -> result <- weight true | _ -> false
Now the experience is much better from C#:
int doors; double weight; if ( vehicle.TryGetCar( out doors ) ) { driveCar( doors ); } else if ( vehicle.TryGetLorry( out weight ) ) { driveLorry( weight ); }
and the new methods are usable from F# as well, although you’d probably prefer to use match:
// Use TryGet approach - returns bool, int let isCar, doors = vehicle.TryGetCar() // Use match approach - more natural for F# match vehicle with | Car doors -> driveCar(doors) | Lorry weight -> driveLorry(weight)