Why the fixation on pattern matching? Don't get me wrong, I enjoy the benefit it provides, but for OO code multiple dispatch is a more elegant and idiomatic way to solve the "I don't want to implement the visitor pattern" problem. FYI, I recently found out that C# can actually do multiple dispatch https://blogs.msdn.microsoft.com/shawnhar/2011/04/05/visitor...
What I want to do is create proper sum types. For some reason most OO languages were designed around the idea that being "open for extension" was a good thing. Normally if I make a base class, I want a closed and known set of sub classes. E.g. if I make a payment method then I want to make a Credit or Invoice kind where one requires Credit Card details and the other requires an address. In F# that's a simple sum type. In C# making a sum type is a convoluted mess of making an outer base class and private nested sub classes.
Here a sum type with the two cases plus an exhaustive pattern matching switch would have solved in 10 lines what takes me 300 lines to do in C#. I think the concept of "fixed" or "closed" hierarchies is foreign enough to OO that it probably needs to be a completely separate concept from the normal types. F# shows that it can be implemented as sugar on top of the regular CLR type system. All that's needed is that some keyword such as "enum class" is converted into a sealed hierarchy of plain record classes with no methods on them. The switch statement we already have can then treat these "enum classes" specially by checking that all cases are matched.
A sketch solution for C# could look like
enum class PaymentMethod
{
CreditCard(CrediCardDetails cc),
Invoice(Address billingAddress),
}
// later
switch (paymentMethod)
{
case CreditCard(ccdetails):
Console.WriteLine($"Creditcard expiring {ccdetails.ExpiryDate}");
case Invoice(addr):
Console.WriteLine($"Bill to {addr.Name}");
}
Writing the above in current C# would require significantly more boilerplate.
This isn't the best pattern to use in all situations: but in many cases when the types are merely data carriers it doesn't make sense to put the logic in the class as OO prescribes, so the FP recipe works much better.