Why on earth did they decide boxing by default was a sensible design decision...

We have been pushing toward higher performance for years and this is a performance pitfall for unions would are often thought of as being lighter weight than inheritance hierarchies.

F# just stores a field-per-case, with the optimization that cases with the same type are unified which is still type safe.

Hi there! One of the C# language designers here, working on unions. All the different options have tradeoffs. As an example, the non-boxing options tear, which can be problematic. And, we have a lot of experience implementing the simple, reference-type, approach for types that make a lot of sense to people, but then adding a lightweight, value-type version for people who care about that later. See tuples, as well as records.

I expect the same will old here. But given the former group is multiple orders of magnitude higher than the latter, we tend to design the language in that order accordingly.

Trust me, we're very intersted in the low-overhead space as well. But it will be for more advanced users that can accept the tradeoffs involved.

And, in the meantime, we're designing it in C#15 that you can always roll the perfect implementation for your use case, and still be thought of as a union from teh language.

> with the optimization that cases with the same type are unified which is still type safe.

To be clear, this requires explicitly using the same field name as well.

From what I've read, this is for the first implementation of unions, to reduce amount of compiler work they need to do. They have designed them in a way they can implement enhancements like this in the future. Things like non-boxing unions and tagged unions / enhanced enums are still being considered, just not for this version.

This is the general pattern of how the C# team operates, IME.

    "Never let perfect be the enemy of good"
Very much what I've seen from them over the years as they iterate and improve features and propagate it through the platform. AOT as an example; they ship the feature first and then incrementally move first party packages over to support it. Runtime `async` is another example.