back
282 comments
I love C# and in every iteration we're getting more and more features to get C-like performance in a lot of scenarios. C# does it really well because if your problem isn't performance/memory-constrained, you can ignore these features and fallback on the language's natural ease of use.
I daylight as a .NET dev professionally. I completely agree with what you have wrote, but I do not think C# is particularly unique in that regard. I would say many common compiled languages are on the same path, e.g., Swift, Java, Kotlin, etc.. As time progresses, I am finding it harder to justify using C# for a greenfield project.
Do you think by now C# has left Java behind in features and performance?
Finally C# will have a more idiomatic way to represent this classic code example from “The Daily WTF” in 2005 —

    enum Bool 
    { 
        True, 
        False, 
        FileNotFound 
    };
See https://thedailywtf.com/articles/what_is_truth_0x3f_
What about Microsoft's own "MsoTrioState"? https://learn.microsoft.com/en-us/dotnet/api/microsoft.offic...

  enom MsoTrioState {
    Toggle,
    Mixed,
    True,
    False,
    CTrue
  };
Awful as it is, I can easily imagine how it happened... Probably says more about me than the code.
About 12 years ago I went to go and work on some legacy C++ software when I was very green. We had a big code base and it had enum Bool { False = 0, True = 1 } everywhere. I thought it was a good idea to rationalise this since we had conversions all over the place to the modern bool type.

So I suggested it, got a PR up (bit painful) it got reviewed, went in, everything worked fine, and we came about a week towards releasing the product (6 monthly releases) before someone noticed that we couldn’t load files from previous versions. Turned out that we wrote lots of these old Bool types to binary data files and so the 4 byte data was now being read as 1 byte data. Oops. Reverted the whole lot. Lesson in humility!

And as always when there is a mix of state and result, i post rop https://fsharpforfunandprofit.com/rop/
Could False = FileNotFound and this is a case of AST symbol constraints?
I've waited for union types on C# so long that I don't even care about syntax anymore. Just give us something that works. So, I appreciate the effort, I know it's taken at least a decade to get it into this shape, and much thought has gone into it. Kudos to the team.
F# has had this for decades, C# is basically just slowly becoming F# with a C-style syntax. Not complaining though, most teams aren't switching languages so getting these features where people actually work is better than nothing.
F# leads the way and C# slowly catches up, as always. Yet for some reason, C# still gets all the mindshare.
Haskell, OCaml, Erlang lead the way and Rust, Zig and Go get all the mindshare. I feel like its a common pattern for more experimental languages to pioneer features and other languages to copy the features and bring them to a C style syntax that the majority of devs are familiar with.
I love F#. It's my go-to language and one that I work with every day. Personally I feel that IDE support (as in perf, QoL features, etc.) is the only area it lags behind C#, and outside of that it's a clear winner for everything that I want to do with it.
Microsoft management has decided CLR has a new meaning, C# Language Runtime.

VB, C++/CLI and F# are only there because existing customers.

They have always behaved as if it had been a mistake to promote F# from research project into VS 2010 as an official language.

Since then it has been something that the teams never knew how to sell to the .NET customer base, pivoting from being only libraries for C# and VB, write unit tests, Web development, data analysis, whatever might make it.

However it was Standard ML, Miranda, Hope, OCaml and Haskell that lead the way, we aren't still fully there.

What types of problems are better solved in F# than C#?

Is having a combination of F# and C# in a single codebase possible? Is it recommended?

C# gets all the mindshare because it's easier to understand and use on average.

We can all agree that F# is more clever and concise. No one is dying on that hill. But in terms of hacking your way through the customer requirements and working with a team of other humans, it cannot hold ground in the same way.

There is certainly not some concerted effort or lack of care involved. Microsoft could 10x the marketing budget around F# and the adoption rate probably wouldn't budge.

Most people don't know that there are fundamentally two different kinds of "union" types: "tagged unions" and "untagged unions". Now .NET is introducing tagged unions, but unfortunately they stick to the popular tradition of calling them just "unions", which greatly adds to the confusion.

To clarify, these tagged unions are fundamentally different from the untagged unions that can be found in languages like Typescript or Scala 3.

Tagged unions (also called "discriminated unions" or "sum types") are algebraic data types. They act as wrappers, via special constructors, for two (or more) disjoint types and values. So a tagged union acts like a bag that has two (or more) labelled ("tagged") slots, where each slot has exactly one type and exactly one of these slot can take a value.

Untagged unions are set theoretic (rather than algebraic) data types. They don't require wrapping via a special constructor. They behave like the logical OR. They are not disjoint slots of a separate construct. A variable or function x with the type "Foo | Bar" can be of type Foo, or Bar, or both. To access a Foo method on x, one has to first perform a typecheck for Foo, otherwise the compiler will refuse the method call (since x might only have the type Bar which would produce an exception). If a variable is of type A, it is also of type A|B ("A or B"). There are also intersection types (A&B) which indicate that something has both types rather than at least one, and complement/negation types (~A indicates that something is of any type except A). Though the latter are not implemented in any major language so far.

These are not what are commonly called tagged unions. It's actually closer to an untagged union.

The C# union does not store any discriminator. Just look at the implementation - it's a single `object?` field.

The discriminative part is handled by run-time type information, which is stored in the object itself, not the union - which is why the C# built-in implementation requires boxing.

Also, you can use the same class/record type ("discriminator") in several different unions - again, a feature which ADTs/sum-types/tagged unions in most functional languages do not have.

You can even store one single object (ie. identical by reference equality) in several different union values at the same time, theoretically, which in combination with mutability is... uhh, certainly not common functional/mathematical semantics.

Underlyingly, all unions are the same concept, exposed by C's `union` keyword, namely overlapping memory (as opposed to `struct`s, which are sequential memory). You can add a discriminator tag to your unions, your call. If it is impossible for there to be ambiguity (e.g. you track the state somewhere else, and you're dealing with a large array), it may be redundant / memory inefficient to tag every item.

Having these tags be a language construct is just a DX feature on top of unions. A very handy one, but it doesn't make tagged and untagged unions spring from different theoretical universes. I enjoy the ADT / set-theoretic debate as much as the next PL nerd, but theory ought to conform to reality, not vice versa.

About time. TypeScript and Rust proved how much cleaner code gets when you can model "this OR that" at the type level. The real test will be whether library authors start using them in public APIs or if they stay a curiosity.
Standard ML proved this 50 years ago.
Why would the default language-level union keyword implementation force boxing? Seems like a crazy decision if it could just also implement the HasValue/TryGet itself and avoid it?
Hi there! C# language designer here, and one of the people working on unions.

Boxing is not something inherently to be avoided. It actually can work better in many (most?) use cases, and avoids a lot of problems that non-boxing approaches often cause (like tearing and copy costs).

It's try that the non boxing pattern could be implemented by us. And it's very reasonable that that is something we may do post this release. However, it's a non-trivial area. There's no one correct 'non-boxed' implementation. For example, do you have separate fields for all your unmanaged data? or do you have a blob of bytes that is large enough to align all your unmanaged data from teh largest set of of unmanaged fields, and you unsafe index into that?

Similar question for managed data. Do you have strongly typed fields for that data? Or do you attempt to use objects, to compact to as little space as possible? The former avoids casting costs. The latter allows you to minimize space. You can also potentially use unsafe casts. But those might introduce memory holes in tearing situations. etc. etc.

Because of this, i think the best outcome is to define the pattern (which we've done) and then use generators to allow you to control precisely the impl strategy, giving you all the bells and knobs you want to best fit your domain.

As a big user and fan of c# but this is a miss, as it always boxes value types.
It always boxes them TODAY. Lately the team has been releasing an MVP and improving stuff (like perf) later on. I wouldn't be surprised if they do the same thing here, as noted in the article you can already work around that yourself.
Hi there, one of the C# lang designers here. I discuss this a bit in https://news.ycombinator.com/item?id=48255658.

Contrary to what a lot of people guess, boxing is actually a really good strategy most of the time. And, is indeed what many people are doing here anyways. The design supports a pattern that allows for non-boxing, and I expect that we will both supply an implementation for that with reasonable defaults, and that source generators will be a great way to augment this to get highly specialized impl strategies for non-boxing depending on the varying domain needs any specialized customer may have.

There is the non boxing option, and this is the first iteration of the work, it's not one and done.
It is a downer, but I would like to test the performance first in the practical scenario. I've been working on a project where tagged unions would literally saved us from complexity. If that to happen again later I would go for boxed implementation and swallow up the penalty.
When I cared about C# (which is no longer the case), I was lightly involved in the discussion for this and sibling features - mostly theorycrafting exactly your ask: the JIT team very succinctly expressed extreme disinterest in adding support of any kind.

The C# compiler could do it to a degree, but there would be too many caveats to make it actually useful. Unless the JIT team has a change of heart, you're probably never going to see this.

I'm glad to finally see this making it's way into C#. Not so much because I want to use unions purely in C#. But because I want to be able to define them when interfacing with other languages.
AFAICT, this means you won’t be able to define Either<string, string>, which is definitely a thing you sometimes want to do.
It seems like if you wrap both in a record then it should be possible:

    public record Left<T>(T Value);
    public record Right<T>(T Value);
    public union Either<L, R>(Left<L>, Right<R>);
Shame it'll be like 3 years until System.Text.Json sees support for serialization or exporting schemas for them..
C# is my strongest and favorite language. That said, it's frustrating that the C# framework ecosystem lacks solid options. MAUI is especially half-baked, and I'm really starting to doubt whether I should continue using XAML
C# is such an underrated language.

A nice balance of language features and legibility.

Wow 2016 would have loved this news.
sadly there is no ad hoc way of defining them. I do not care if they are boxed. I just want to say Success or Error1 or Error 2. Maybe I can live with or | or other stuff but not having ad hoc unions is basically not cool.
Wow! I remember unions back in my Uni days. So many years ago.
How about F# compatability?
I wish the syntax looked more like typescript. This will confuse my eyes for a while.
If C# can get them then Golang can get them as well!

Come on Gophers! It's time.

Boxed, and needs complex incantations to avoid the boxing. Meh.
I mean yes, but also: uh-oh. I'm looking forward to reading some code that is even more confusing than the code I'm already reading.

Not entirely convinced that I see the usecase that makes up for the potential madness.

Did Anders Hejlsberg die, or something?
I used to see some excitement around .net core several years ago. I haven’t heard or seen much in the wild. Is anyone using .net on systems other than windows nowadays?