Starting with C# 7.0, there is basic pattern matching support. I want to look at using this to interop with F# Discriminated Unions, and see what consuming F# code from C# could look like at it’s best.
Here is an example of an F# Discriminated Union (hereafter DU)
type Record = { Name: string; Age: int }
type Abc = | A | B of double | C of RecordFor those unfamiliar with DUs, the Abc type defined above can be either A, B, or C (and nothing else), and in the case of A there is no “payload”, with B and C there is a payload that comes with it.
So given that this compiles down to a sealed class, and that the cases become classes, we can use the new type pattern in C# to match this with:
type Record = { Name: string; Age: int }
type Abc = | A | B of double | C of RecordThere are some things to notice here:
1. It’s really nice
The code here is clean I’d say, we are matching on type and can either ignore the result of the cast using _ or we can take it as a named variable (like with c in this example).
2. We don’t get the safety we have in F#
In F#, when handling DUs the compiler ensure that we have handled all cases, in C#, these safety checks aren’t enforced.
3. What about case A!
Yeah, so this is where it’s not perfect. The F# compiler does generate a type for case A however it is marked as internal and therefore is not accessible for us to match against, however there are a few options, I’ll let you pick which is your favourite:
voidvoidSpecifies a return value type for a method that does not return a value. HandleAbcvoid HandleAbc(Abc abc)(Abc abc){ switch (abc) { // Option 1: case var xvoid HandleAbc(Abc abc) when x.IsAvoid HandleAbc(Abc abc):void HandleAbc(Abc abc) ConsoleConsoleRepresents the standard input, output, and error streams for console applications. This class cannot be inherited..WriteLinevoid Console.WriteLine(string? value)Writes the specified string value, followed by the current line terminator, to the standard output stream.Parametersvalue — The value to write.ExceptionsIOException — An I/O error occurred.("A"); break; // Option 2: case var xvoid HandleAbc(Abc abc) when x ==void HandleAbc(Abc abc) Abcvoid HandleAbc(Abc abc).Avoid HandleAbc(Abc abc):void HandleAbc(Abc abc) ConsoleConsoleRepresents the standard input, output, and error streams for console applications. This class cannot be inherited..WriteLinevoid Console.WriteLine(string? value)Writes the specified string value, followed by the current line terminator, to the standard output stream.Parametersvalue — The value to write.ExceptionsIOException — An I/O error occurred.("A"); break; }
switch (abc.Tagvoid HandleAbc(Abc abc)) { // Option 3: case abc.Tagscase abc.Tags.A:.Acase abc.Tags.A::void HandleAbc(Abc abc) ConsoleConsoleRepresents the standard input, output, and error streams for console applications. This class cannot be inherited..WriteLinevoid Console.WriteLine(string? value)Writes the specified string value, followed by the current line terminator, to the standard output stream.Parametersvalue — The value to write.ExceptionsIOException — An I/O error occurred.("A"); break; }}Let me know in the comments what your preference is, or is there a better option?
Thanks for reading!
Comments