Type payment = Invoice(Address) | Card(CardDetails)
And then match/switch on those things, while the compiler will tell me if I did something wrong such as not handle one case. Adding a third type of payment (Cash) is as simple as adding "| Cash" to the definition - and the compiler will tell me exactly where I haven't handled cash payments.A simple smell test for a programming language is this: if describing the above takes more than the above code - your language has a wart. If you try to describe this properly in C# you'll be writing an abstract outer class and N concrete inner classes, plus boilerplate for comparisons and so on. You'll be looking at likely 50-100 lines of code where no single line of code actually shows whan what you are modeling. This is the terrible part of OO. I'm sure there are a few cases to show an FP weakness handled elegantly by OO - but I'm equally sure those cases are fewer.
Also, there is nothing particularly "FP" about ADT's. They are just common in FP languages. Any OO language with a sum type and exhaustive matching can do this. But typically, such as for Java, C++ and C# they don't (yet). There are languages that support this that aren't FP (but not necessarily OO either) such as Rust and Kotlin.