11 ms·
.NET (OK, C#) finally gets union types
- Rapzid 5mo agoShame it'll be like 3 years until System.Text.Json sees support for serialization or exporting schemas for them..
- 3plefly 5mo ago[dead]
- biglyburrito 5mo agoOr being able to debug exceptions as easily as in Newtonsoft.Json.
- SuperV1234 5mo agoBoxed, and needs complex incantations to avoid the boxing. Meh.
- Metasyntactic 5mo agoHi there! One of the C# lang designers here. I discuss that here: https://news.ycombinator.com/item?id=48255658 https://news.ycombinator.com/item?id=48255658 Note that my expectation would be that the non-boxed form would be as trivial as adding `[NonBoxedUnion(SomeImplStrategyChoiceEnum)]` (or `[NonBoxedUnion]` for some default strategy choices that likely are ok). This would give you extremely fine grained flexible choice on how you wanted your non-boxing union to work. There's no single right answer. There are just tradeoffs in terms of space/speed/copying-costs/memory-safety/etc. I think it would make the most sense as people who care about boxing will have very different views and needs in terms of things like space, casting costs, copying speed etc. The vast vast majority of users do not need to care at all. And for that, a boxed approach works exceptionally well.
- munchler 5mo agoF# leads the way and C# slowly catches up, as always. Yet for some reason, C# still gets all the mindshare.
- correct_horse 5mo agoHaskell, 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.
- cultofmetatron 5mo agoRust and Zig brought new ideas for memory management that Haskell, OCaml, Erlang sidestep having garbage control. its honestly amazing to me that they managed to get the adoption they have while being so innovative. I say this as a fulltime elixir dev.
- ShalokShalom 5mo agoYou would be surprised to see, how OCaml and the BEAM specifically think about memory.
- unclad5968 5mo agoWhat new ideas did Zig bring to memory management?
- hahn-kev 5mo agoI'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.
- Quarrelsome 5mo agoI 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.
- vips7L 5mo agoUnion/sum types are generally a good thing. Even Java added them. They tend to be worth “the madness”. Now the rest of all the crazy C# features might be a different question.
- dgellow 5mo agoWhat features do you see as crazy?
- munchler 5mo agoAll the weird cruft around nullability, for starters. Once again confirming that allowing null references is usually a mistake.
- dgellow 5mo agoDo you mean the implicit nullable types? Now that you can make nullable explicit instead I really don’t have much issues with it. It is part of the type system, as it should, and you have null coalescing operators. Is it still problematic or are you dealing with older codebases where you cannot set the nullable pragma?
- moomin 5mo agoAFAICT, this means you won’t be able to define Either<string, string>, which is definitely a thing you sometimes want to do.
- throw1234567891 5mo agobut can you define T1 and T2 of string, then use Either<T1, T2>?
- eterm 5mo agoCould you be clearer about what you mean, since string is a sealed type in C#, so what exactly do you mean T1 and T2 of string?
- rawling 5mo agoA record wrapping a string, indicating what the string represents, so you can't mix it up with a different thing also represented by a string.
- eterm 5mo agoYes, you can have two different record types which both wrap a string value. As a (bad) trivial example, you could wrap reading a file in this kind of monstrosity: var fileResult = Helpers.ReadFile(@"c:\temp\test.txt"); Console.WriteLine("Extracted:"); Console.WriteLine(Helpers.ExtractString(fileResult)); public record FileRead(string value); public record FileError(string value); public union FileResult(FileError, FileRead); public static class Helpers { public static FileResult ReadFile(string fileName) { try { var fileResult = System.IO.File.ReadAllText(fileName); return new FileRead(fileResult); } catch (Exception ex) { return new FileError(ex.Message); } } public static string ExtractString(FileResult result) { return result switch { FileError err => $"An Error occured: {err.value}", FileRead content => content.value, _ => throw new NotImplementedException() }; } } Now, such an example would be an odd way to do things ( particuarly because we're not actually avoiding the try/catch inside ), but you get the point. Both FileRead(string value) and FileError(string value) wrap strings in the same way, but are different record types, and the union FileResult ties them back together in a way where you can tell which you have. It's more useful implemented a level deeper, so that the exception is never raised and caught, because exceptions aren't particularly cheap in .NET.
- deleted 5mo ago[deleted]
- le-mark 5mo agoI 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?
- dgellow 5mo agoIt’s huge in the game dev world, with Unity and Godot. .net also had a reasonable community on mobile for a while thanks to Xamarin, but I cannot imagine that many people using it for new mobile projects in 2026 (outside of game dev I mean). It’s a very decent language (I mean C#) and runtime, I wish it had more market share in the startup world.
- smlavine 5mo agoAn enterprise shop I co-op'd at was porting one of their apps from Xamarin to MAUI when I worked there, but certainly it doesn't have much mindshare (if any) amongst SE undergrads at my university.
- unethical_ban 5mo agoSomeone I know who works with .net says that there is still no replacement for full Visual Studio for development, which is Windows only.
- glimshe 5mo agoI 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.
- hirvi74 5mo agoI 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.
- za3faran 5mo ago> As time progresses, I am finding it harder to justify using C# for a greenfield project. Are you able to elaborate why? Just curious.
- antonymoose 5mo agoNot OP here - but for me it’s the open source ecosystem. Java just wins in terms of scope, scale, and stability. I love C# the language, but the ecosystem is a ghetto.
- Rohansi 5mo agoI see this reason a lot but what are some actual examples of what is lacking in the .NET ecosystem vs. Java?
- davewritescode 5mo agoLarge Apache ecosystem (Spark, Flink, Pinot) is completely missing and third-party SDKS (looking at you AWS) almost ALWAYS have worse SDKs. The java Kinesis consumer and producer libraries are amazing, the ones for C# are simple wrappers around the AWS APIs which means there's a few foot guns waiting for developers to run into, even if they should know better.
- deadeye 5mo agoI wish the syntax looked more like typescript. This will confuse my eyes for a while.
- ameliaquining 5mo agoIt works quite differently from TypeScript, so the syntax used there wouldn't have worked.
- rohitsriram 5mo agoF# 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.
- jdw64 5mo agoC# 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
- makotech221 5mo agoWinforms, wpf, blazor, maui, avalonia, what are you talking about
- jdw64 5mo agoAlright. I'm actually fine with WinForms and WPF since my factory floor codes depend on them. But the reality is they aren't expressive enough for modern UIs. XAML is an issue, and WPF is boilerplate hell. But then Blazor is too heavy, MAUI is broken and buggy, Avalonia is underwhelming, and WinUI 3 is an absolute nightmare.
- sedatk 5mo ago> Avalonia is underwhelming How? Can you elaborate?
- jdw64 5mo ago[dead]
- vitally3643 5mo agoNot OP, but I've found Avalonia to be pretty much a direct replacement for WinForms. I mean that both as a compliment and a deserved insult. It's not the WinForms we wanted, but it is the one we deserve. More seriously, it has all the strengths and weaknesses of WinForms and feels about exactly as unfinished and rough as WinForms. I still have to implement custom widgets that i would have expected to be included out of the box. It's nice that it's cross-platform, though with all the rough edges that cross-platform .net still has. It really, truly feels exactly like every C# UI framework I've ever used in the last 20 years: almost good, not quite finished, and takes an amount of effort that is just unreasonable compared to any other language/framework of any age. I've been a C# dev for most of my career. I have more fun writing UIs from scratch by drawing individual pixels in C++ than any C# UI.
- antonvs 5mo agoDid Anders Hejlsberg die, or something?
- jafffsuds 5mo agoHe's been busy with the typescript-go project
- sedatk 5mo agoI'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.
- LAC-Tech 5mo agoWow 2016 would have loved this news.
- cubefox 5mo agoMost 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.
- troad 5mo agoUnderlyingly, 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.
- cubefox 5mo ago
- bartwe 5mo agoAs a big user and fan of c# but this is a miss, as it always boxes value types.
- Izikiel43 5mo agoThere is the non boxing option, and this is the first iteration of the work, it's not one and done.
- zamalek 5mo agoWhen 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.
- za3faran 5mo agoWhat do you use these days language-wise?
- zamalek 5mo agoI'm part of The Rust Evangelism Strikeforce. In all seriousness, whatever suits the task well. Rust is for passion projects where I want to enjoy coding something; I find Go to be more of a direct competitor to C# - but it has the edge due to lacking exceptions (which is 15 years wasted of my life, never again). I'm always searching for something better, though.
- hahn-kev 5mo agoIt 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.
- caioricciuti 5mo agoAbout 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.
- frutiger 5mo agoStandard ML proved this 50 years ago.
- pdimitar 5mo agoIf C# can get them then Golang can get them as well! Come on Gophers! It's time.
- LeonB 5mo agoFinally 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_ https://thedailywtf.com/articles/what_is_truth_0x3f_
- mycall 5mo agoCould False = FileNotFound and this is a case of AST symbol constraints?
- rich_sasha 5mo agoAwful as it is, I can easily imagine how it happened... Probably says more about me than the code.
- tgv 5mo agoYou're a true Romantic: Idealist and Pragmatist in one.
- t-writescode 5mo agoWhat about Microsoft's own "MsoTrioState"? https://learn.microsoft.com/en-us/dotnet/api/microsoft.office.core.msotristate?view=office-pia https://learn.microsoft.com/en-us/dotnet/api/microsoft.offic... enom MsoTrioState { Toggle, Mixed, True, False, CTrue };
- 8n4vidtmkvmk 5mo agoIs it called "trio" state because 3 of the 5 states are not supported? I also like how True is -1. Beautiful all around!
- saidnooneever 5mo agonote CTrue, the one true true
- ComputerGuru 5mo agoWhy 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?
- Metasyntactic 5mo agoHi 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.
- ShalokShalom 5mo agoCan we expect that the C# development team works with the F# crowd, to make this copied feature work on both?
- Metasyntactic 5mo agoYes. We do. And unions should work well in F#. It's designed to be a very easy pattern for all CLR languages and compilers to understand (including F#).
- 5mo ago
- msie 5mo agoWow! I remember unions back in my Uni days. So many years ago.
- spicyusername 5mo agoC# is such an underrated language. A nice balance of language features and legibility.
- patrickndaye919 5mo ago[flagged]
- merb 5mo agosadly 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.
- ShalokShalom 5mo agoHow about F# compatability?
- useKiri 5mo ago[flagged]