Union types in C# 15 (devblogs.microsoft.com)
114 points by 0x00C0FFEE 4 days ago
merb 3 minutes ago
Sad part is, is that ad hoc unions probably won’t make it into v1. That is probably one of the only feature why I like typescript. Because I can write result types in a good way without creating thousands of sub types. It’s even more important when using Promises and not having checked exceptions.
amluto an hour ago
Hmm, they seem to have chosen to avoid names to the choices in the union, joining C++ variants and (sort of) TypeScript unions: unions are effectively just defined by a collection of types.
Other languages have unions with named choices, where each name selects a type and the types are not necessarily all different. Rust, Haskell, Lean4, and even plain C unions are in this category (although plain C unions are not discriminated at all, so they’re not nearly as convenient).
I personally much prefer the latter design.
tialaramex an hour ago
The C and Rust union types are extremely sharp blades, enough so that I expect the average Rust beginner doesn't even know Rust has unions (and I assume you were thinking of Rust's enum not union)
I've seen exactly one Rust type which is actually a union, and it's a pretty good justification for the existence of this feature, but one isn't really enough. That type is MaybeUninit<T> which is a union of a T and the empty tuple. Very, very, clever and valuable, but I didn't run into any similarly good uses outside that.
masklinn 35 minutes ago
Unions can be used as a somewhat safer (not safe by any means but safer), more flexible, and less error-prone form of transmute. Notably you can use unions to transmute between a large type and a smaller type.
That is essentially the motivation, primarily in the context of FFI where matching C's union behaviour using transmute is tricky and error-prone.
randomNumber7 6 minutes ago
repelsteeltje 31 minutes ago
Not sure, but I think C++ actually does allow std::variant with multiple choices using the same type. You might not be able to distinguish between them by type (using get<Type>()), but you can by position (get<0>(), get<1>(), ...)
tialaramex 4 hours ago
I don't love OneOrMore<T>
It's trying to generalize - we might have exactly one T, fine, or a collection of T, and that's more T... except no, the collection might be zero of them, not at least one and so our type is really "OneOrMoreOrNone" and wow, that's just maybe some T.
layer8 31 minutes ago
> so our type is really "OneOrMoreOrNone"
If I understand correctly, it’s actually OneOrOneOrMoreOrNone. Because you have two different distinguishable representations of “one”.
The only reason to use this would be if you typically have exactly one, and you want to avoid the overhead of an enumeration in that typical case. In other words, AnyNumberButOftenJustOne<T>.
merb 2 hours ago
OneOrMore is more or less an example from the functional world. i.e.:
https://hackage.haskell.org/package/oneormore or scala: https://typelevel.org/cats/datatypes/nel.html
it's for type purists, because sometimes you want the first element of the list but if you do that you will get T? which is stupid if you know that the list always holds an element, because now you need to have an unnecessary assertion to "fix" the type.
dasyatidprime 2 hours ago
The NonEmptyList in Cats is a product (struct/tuple) type, though; I assume the Haskell version is the same. The type shown in the blog post is a sum (union) type which can contain an empty enumerable, which contradicts the name OneOrMore. The use described for the type in the post (basically a convenience conversion funnel) is different and makes sense in its own right (though it feels like kind of a weak use case). I'm not sure what a good name would've been illustratively that would've been both accurate and not distracting, though.
merb 17 minutes ago
CharlieDigital 3 hours ago
`OneOrMore<T>` was an example of using `union` types.
You are free to call it `public union Some<T>(T, IEnumerable<T>)`
rafaelmn 2 hours ago
> OneOrMoreOrNone
So IEnumerable<T> ? What's up with wrapping everything into fancy types just to arrive at the exact same place.
paulddraper 2 hours ago
I went to prove you wrong…
And you’re exactly right.
It’s not “one or more.”
It’s “one or not one.”
Need two or not two.
recursive an hour ago
Hotdog or not hotdog.
karmakaze 3 days ago
I haven't read this in detail but I expect it to be the same kind of sealed type that many other languages have. It doesn't cover ad-hoc unions (on the fly from existing types) that are possible in F# (and not many non-FP languages with TypeScript being the most notable that does).
CharlieDigital 6 hours ago
IME, this is a good thing.
The problem with ad-hoc unions is that without discipline, it invariably ends in a mess that is very, very hard to wrap your head around and often requires digging through several layers to understand the source types.
In TS codebases with heavy usage of utility types like `Pick`, `Omit`, or ad-hoc return types, it is often exceedingly difficult to know how to correctly work with a shape once you get closer to the boundary of the application (e.g. API or database interface since shapes must "materialize" at these layers). Where does this property come from? How do I get this value? I end up having to trace through several layers to understand how the shape I'm holding came to be because there's no discrete type to jump to.
This tends to lead to another behavior which is lack of documentation because there's no discrete type to attach documentation to; there's a "behavioral slop trigger" that happens with ad-hoc types, in my experience. The more it gets used, the more it gets abused, the harder it is to understand the intent of the data structures because much of the intent is now ad-hoc and lacking in forethought because (by its nature) it removes the requirement of forethought.
"I am here. I need this additional field or this additional type. I'll just add it."
This creates a kind of "type spaghetti" that makes code reuse very difficult.So even when I write TS and I have the option of using ad-hoc types and utility types, I almost always explicitly define the type. Same with types for props in React, Vue, etc; it is almost always better to just explicitly define the type, IME. You will thank yourself later; other devs will thank you.
let_rec 7 hours ago
> ad-hoc unions (on the fly from existing types) that are possible in F#
Are you sure? This is a feature of OCaml but not F# IIUIR
Edit: https://github.com/fsharp/fslang-suggestions/issues/538
owlstuffing 6 hours ago
> It doesn't cover ad-hoc unions
Yes and no. C# unions aren’t sealed types, that’s a separate feature. But they are strictly nominal - they must be formally declared:
union Foo(Bar, Baz);
Which isn’t at all the same as saying: Bar | Baz
It is the same as the night and day difference between tuples and nominal records.orthoxerox 7 hours ago
Third paragraph from the top:
> unions enable designs that traditional hierarchies can’t express, composing any combination of existing types into a single, compiler-verified contract.
SideburnsOfDoom 7 hours ago
It's very unclear which you mean by that.
To me that "compiler-verified" maps to "sealed", not "on the fly". Probably.
Their example is:
public union Pet(Cat, Dog, Bird);
Pet pet = new Cat("Whiskers");
- the union type is declared upfront, as is usually the case in c#. And the types that it contains are a fixed set in that declaration. Meaning "sealed" ?
pjc50 7 hours ago
orthoxerox 3 hours ago
dathinab 6 hours ago
it's basically `union <name>([<type>],*)`, i.e.
=> named sum type implicitly tagged by it's variant types
but not "sealed", as in no artificial constraints like that the variant types need to be defined in the "same place" or "as variant type", they can be arbitrary nameable types
mpawelski 4 hours ago
I'm pretty sure at one point there was proposal that allowed declaring something like `int or string`. Not sure what happened with it though.
mwkaufma 5 hours ago
Looks like it's "just" type-erasure / syntactical sugar. E.g. value types are boxed.
functional_dev 3 hours ago
Right, the default boxes into heap, but unions are different. Some languages pack them as a flat struct (tag + payload, no allocation).
Here is visual layout if anyone is interested - https://vectree.io/c/memory-layout-tagging-and-payload-overl...
AndrewDucker 5 hours ago
Yes, but see the section on custom unions* - you can write non-boxing unions/generators.
* https://devblogs.microsoft.com/dotnet/csharp-15-union-types/...
mwkaufma 3 hours ago
Yes, there's a compat-shim in the stdlib/runtime, but not in the language syntax. E.g. it by-definition won't do escape-analysis and optimize discriminated value-types with the first-class keyword.
celeries 5 hours ago
Yes, but that's just the default behavior. You can implement your own non-boxing version for performance critical applications.
algorithmsRcool 2 hours ago
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.
zigzag312 33 minutes ago
jcmontx 6 hours ago
So they finally took all of the cool features from F#. What's missing? The pipe operator for railway oriented programming?
algorithmsRcool 5 hours ago
Well off the top of my head...
Active patterns, computation expressions, structural typing, statically resolved type parameters, explicit inlining, function composition, structural equality, custom operators and much richer generators.
JMKH42 5 hours ago
type providers, units of measure, active patterns, complete type inference.
Not sure I would want the last thing in C#, I think having boundaries at the function signature for that.
recursive 5 hours ago
Units of measure
owlstuffing 5 hours ago
F# units are handy, but nothing like Manifold units (Java):
https://github.com/manifold-systems/manifold/tree/master/man...
DeathArrow 5 hours ago
I love it, but I see a downside, though: unions are currently implemented as structs that box value types into a Value property of type object. So there can be performance implications for hot paths.
IcyWindows 4 hours ago
The article mentions that one can implement a union type that doesn't do boxing.
DeathArrow 5 hours ago
This is HUGE! Now we can use mostly functional programming in C#. This feature was requested since many years ago.
The only thing I wish now is for someone to build a functional Web framework for C#.
littlecranky67 an hour ago
Minimal API is pretty functional.
98347598 7 hours ago
It's very disappointing that they aren't supporting Rust-style discriminated unions.
_old_dude_ 7 hours ago
In C#, all instances have a class, so there is already a discriminant, the class itself.
In the article, the example with the switch works because it switches on the class of the instance.
cobbal an hour ago
Null doesn't. `union(Int?, String?)` will only have 1 type of null, unlike a proper discriminated union.
hnthrow0287345 7 hours ago
One step at a time
FrustratedMonky 6 hours ago
Is this the last of the F# features to be migrated into C#?
What a missed opportunity. I think really F# if you combine all of its features, and what it left out, was the way. Pulling them all into C# just makes C# seem like a big bag of stuff, with no direction.
F#'s features, and also what it did not included, gave it a style and 'terseness', that still can't really be done in C#.
I don't really get it. Was a functional approach really so 'difficult'? That it didn't continue to grow and takeover.
LunicLynx 6 hours ago
You aren’t giving enough credit to the careful evaluation of how this adaption is happening.
So far everything that was added to C# very much reduces the amount of dead boilerplate code other languages struggle with.
Really give it an honest try before you judge it based on the summation of headlines.
dathinab 5 hours ago
> reduces the amount of dead boilerplate code other languages struggle with.
given that most of the thinks added seem more inspired by other languages then "moved over" from F# the "other languages struggle with" part makes not that much sense
like some languages which had been ahead of C# and made union type a "expected general purpose" feature of "some kind":
- Java: sealed interfaces (on high level the same this C# features, details differ)
- Rust: it's enum type (but better at reducing boilerplate due to not needing to define a separate type per variant, but being able to do so if you need to)
- TypeScript: untagged sum types + literal types => tagged sum types
- C++: std::variant (let's ignore raw union usage, that is more a landmine then a feature)
either way, grate to have it, it's really convenient to represent a `TYPE is either of TYPES` relationship. Which are conceptually very common and working around them without proper type system support is annoying (but very viable).
I also would say that while it is often associated with functional programing it has become generally expected even if you language isn't functional. Comparable to e.g. having some limited closure support.
owlstuffing 5 hours ago
In isolation, yes, I agree with you. But in the context of the cornucopia of other "carefully evaluated" features mixed into the melting pot, C# is a nightmare of language identities - a jack of all trades, master of none, choose your dialect language. No thanks.
wvenable 2 hours ago
LunicLynx 4 hours ago
DeathArrow 5 hours ago
zigzag312 6 hours ago
I personally like the direction C# is taking. A multi-paradigm language with GC and flexibility to allow you to write highly expressive or high performance code.
Better than a new language for each task, like you have with Go (microservices) and Dart (GUI).
I'm using F# on a personal project and while it is a great language I think the syntax can be less readable than that of C#. C# code can contain a bit too much boilerplate keywords, but it has a clear structure. Lack of parenthesis in F# make it harder to grasp the structure of the code at a glance.
dathinab 5 hours ago
> big bag of stuff, with no direction.
also called general purpose, general style langue
> that still can't really be done in C#
I would think about it more as them including features other more general purpose languages with a "general" style have adopted then "migrating F# features into C#, as you have mentioned there are major differences between how C# and F# do discriminated sum types.
I.e. it look more like it got inspired by it's competition like e.g. Java (via. sealed interface), Rust (via. enum), TypeScript (via structural typing & literal types) etc.
> Was a functional approach really so 'difficult'?
it was never difficult to use
but it was very different in most aspects
which makes it difficult to push, sell, adapt etc.
that the maybe most wide used functional language (Haskel) has a very bad reputation about being unnecessary complicated and obscure to use with a lot of CS-terminology/pseudo-elitism gate keeping doesn't exactly help. (Also to be clear I'm not saying it has this properties, but it has the reputation, or at least had that reputation for a long time)
FrustratedMonky 5 hours ago
"reputation about being unnecessary complicated and obscure to use with a lot of CS-terminology/pseudo-elitism gate keeping doesn't exactly help"
Probably more this than any technical reason. More about culture and installed view points.
I don't want to get into the objects/function wars, but do think pretty much every technical problem can be solved better with functions. BUT, it would take an entire industries to re-tool. So think it was more about inertia.
Inertia won.
jayd16 2 hours ago
Purity is overrated. C# is a kitchen sink language but you need give credit to the language designers. Compared to C++, for example, C# feels feature rich and consistent even though it abandons purity.
FrustratedMonky 2 hours ago
I think purity matters where you can have the compiler catch problems.
jayd16 2 hours ago
NanoCoaster 6 hours ago
Absolutely agree. Modern C# language design feels very much lacking in vision or direction. It's mostly a bunch of shiny-looking language features being bolted on, all in ways that make the language massively more complex.
Just look at this feature: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/cs...
Was this needed? Was this necessary? It's reusing an existing keyword, fine. It's not hard to understand. But it adds a new syntax to a language that's already filled to the brim, just to save a few keystrokes?
Try teaching someone C# nowadays. Completely impossible. Really, I wish they would've given F# just a tenth of the love that C# got over the years. It has issues but it could've been so much more.
jayd16 2 hours ago
> Try teaching someone C# nowadays
Do you actually have a datapoint of someone failing to understand C# or are you just hyperbolically saying its a big language? The tooling, the ecosystem, the linting, the frameworks. Its a very easy language to get into...
mrsmrtss an hour ago
LunicLynx 6 hours ago
You are looking at it from what you know about C#, the goal is how can you reduce (delete) all this to make the language more accessible.
For you it may be fine to write:
List<string> strs = new List<string>();
And sure if you have been using C# for years you know all the things going on here.
But it shouldn’t be an argument that:
List<string> strs = [];
Is substentionally easier to grasp.
And that has been the theme of all changes.
The example you point out is the advanced case, someone only needs in a very specific case. It does not have a lot todo with learning the language.
The language design team is really making sure that the features work well throughout and I think that does deserve some credit.
NanoCoaster 5 hours ago
xnorswap 5 hours ago
raincole 5 hours ago
raincole 6 hours ago
> Try teaching someone C# nowadays. Completely impossible. Really, I wish they would've given F# just a tenth of the love that C# got over the years
If they actually put effort in F#, it would have reached "unteachable" state already :)
NanoCoaster 5 hours ago
twisteriffic 5 hours ago
> Try teaching someone C# nowadays. Completely impossible.
That isn't a reasonable take. Failing to teach a language by enumerating all its features is an indictment of the instructor and not the language.
NanoCoaster 5 hours ago
Pay08 5 hours ago
This is why I have always been leery of C# and continued using Java instead. C#s development has always seemed very haphazard and kitchen sink mentality to me.
CharlieDigital 6 hours ago
> I don't really get it
To me it makes sense because C# is a very general purpose language that has many audiences. Desktop GUI apps, web APIs, a scripting engine for gaming SDKs, console apps.It does each reasonably well (with web APIs being where I think they truly shine).
> Was a functional approach really so 'difficult'
It is surprisingly difficult for folks to grasp functional techniques and even writing code that uses `Func`, `Action`, and delegates. Devs have no problem consuming such code, but writing such code is a different matter altogether; there is just very little training for devs to think functionally. Even after explaining why devs might want to write such code (e.g. makes testing much easier), it happens very, very rarely in our codebase.raincole 6 hours ago
Union is almost a net positive to C# in my opinion.
But I do agree. C# is heading to a weird place. At first glance C# looks like a very explicit language, but then you have all the hidden magical tricks: you can't even tell if a (x) => x will be a Func or Expression[0], or if a $"{x}"[1] will actually be evaluated, without looking at the callee's signature.
[0]: https://learn.microsoft.com/en-us/dotnet/csharp/advanced-top...
[1]: https://learn.microsoft.com/en-us/dotnet/csharp/advanced-top...
twisteriffic 6 hours ago
From the PoV of someone who uses C# every day those are very strange things to be upset about.
wvenable 2 hours ago
> you can't even tell if...
In the places where that is a thing, I've never needed to care. (Which is kind of the point)
pjmlp 6 hours ago
Microsoft's management has always behaved as if it was a mistake to have added F# into Visual Studio 2010, and being stuck finding a purpose for it.
Note that most of its development is still by the open source community and its tooling is an outsider for Visual Studio, where everything else is shared between Visual Basic and C#.
With the official deprecation of VB, and C++/CLI, even though the community keeps going with F#, CLR has changed meaning to C# Language Runtime, for all practical purposes.
Also UWP never officially supported F#, although you could get it running with some hacks.
Similarly with ongoing Native AOT, there are some F# features that break under AOT and might never be rewritten.
A lost opportunity indeed.
DeathArrow 5 hours ago
>Is this the last of the F# features to be migrated into C#? >What a missed opportunity.
Not adding functional features to F# doesn't mean F# would have gained more usage. And if someone wants to use F#, no one is stopping him or her.
FrustratedMonky 5 hours ago
I meant, 'missed', in that the entire industry would have been better off if F# or functional programming had won out over object oriented/C# styles.
But, that would takes, schools changing, companies changing, everything. So it was really the installed base that won, not what was better.
We'd have to go back in time, and have some ML Language win over C++ .
owlstuffing 6 hours ago
> Pulling them all into C# just makes C# seem like a big bag of stuff, with no direction.
Agreed. Java is on the same trail.
DarkNova6 6 hours ago
Care to elaborate? I think Java is showing remarkable vision and cohesion in their roadmap. Their released features are forward compatible and integrate nicely into existing syntax.
I work much with C# these days and wish C# had as cohesive a syntax story. It often feels like "island of special syntax that makes you fall of a cliff".
mirages 6 hours ago
#define struct union
gib444 6 hours ago
Is C# a great language trapped in a terrible ecosystem? ie would masses use C# if it existed in another ecosystem?
Or is it becoming a ball-of-mud/bad language compared to its contemporaries?
(Honest questions. I have never used .NET much. I'm curious)
JCTheDenthog 6 hours ago
Depends on what you mean by ecosystem, it hasn't been trapped on Windows for about a decade now. The variety of third party libraries available is quite good, while the standard library is robust enough that you don't need NPM nonsense like LeftPad and IsEven and IsNumber.
Are there particular things about the ecosystem that you worry about (or have heard about)? Biggest complaint I would have is that it seems like many popular open source libraries in the .NET ecosystem decide to go closed source and commercial once they get popular enough.
gib444 6 hours ago
Yup, the commercial libraries. That's pretty big. It's nice the standard library has lots of goodies, but I doubt many projects in reality are zero-dependency
(The amount of times I hear "the standard lib is great!" seems more to attempt to defend the plethora of commercial libraries, more than anything)
The community feels rather insular too? The 9-5 dayjob types with employers who don't understand or embrace open source? At my age I can respect that though
And is Postgresql a 2nd-class citizen? If so, your boss will tell you to use SQL Server surely?
I guess it's hard to get a grasp on the state/health of .NET as to me it seems 99.99999% of the code is in private repos companies, as it's not a popular choice for open source projects. Which itself seems like a proxy signal though
CharlieDigital 5 hours ago
jayd16 2 hours ago
celeries 5 hours ago
CharlieDigital 6 hours ago
C# is a language that serves many masters and if you trace the origin of its featureset, you can see why each was created. Take the `dynamic` keyword: created to support interfacing with COM interop easier[0].
It serves many audiences so it can feel like the language is a jack of all trades and master of none (because it is) and because it is largely backwards compatible over its 20+ years of existence.
That said, I think people make a mountain out of a molehill with respect to keyword sprawl. Depending on what you're building, you really only need to focus on the slice of the language and platform you're working with. If you don't want to use certain language features...just don't use them?
I think it excels in a few areas: web APIs and EF Core being possibly the best ORM out there. For me, it is "just right". Excellent platform tooling, very stable platform, very good performance, hot reload (good, but not perfect), easy to pick up the language if you already know TypeScript[1]; there are many reasons it is a good language and platform.
[0] https://learn.microsoft.com/en-us/dotnet/csharp/advanced-top...
mattgreenrocks 3 hours ago
I’m convinced the comment section hates multi-paradigm languages because you can misuse them. And it has features that may not be needed, which triggers this weird purist mentality of, “gee, it would be so much better if it didn’t have feature X.” But oftentimes that’s just pontification for its own sake, and they aren’t really interested in trying it out. Feature X remains something they won’t use, so it should go.
DeathArrow 4 hours ago
>It serves many audiences so it can feel like the language is a jack of all trades and master of none (because it is)
That's why I like it so much. And now, I can write mostly functional code.
>I think it excels in a few areas: web APIs and EF Core being possibly the best ORM out there
It's awesome for web stuff and microservices.
CharlieDigital 4 hours ago
gib444 6 hours ago
> EF Core being possibly the best ORM out there
Is it good at the wrong thing? Eg compare to strongly-typed query generators
CharlieDigital 5 hours ago
jayd16 2 hours ago
Its a great language in a very good ecosystem. Try it. Its great.
It has a bad rep because Microsoft could Microsoft as they do.
delta_p_delta_x 2 hours ago
> terrible ecosystem
.NET is a fantastic ecosystem. Has a decent build and dependency system (NuGet, dotnet run/build, declarative builds in XML). Massive standard library, with a consistent and wide focus on correctness, ergonomics, and performance across the board.
You can write everything in many languages, all on the same runtime: business logic in C#; hot paths interfacing with native libraries in C++/CLI; shell wrappers in PowerShell, document attachments with VB, data pipelines in F#.
I feel more people should use it, or at least try it, but sadly it is saddled with the perception that it is Windows-only, which hasn't been true for a decade (also, IMO, not necessarily a negative, because Windows is a decent OS, sue me).
ux266478 an hour ago
> but sadly it is saddled with the perception that it is Windows-only, which hasn't been true for a decade
In my experience it does not work very well outside of the sanctioned Linux distributions. Quirky heisenbugs and nonsensical crashes made it virtually unusable for me on Void. I doubt that's changed in the years that have since passed.
> not necessarily a negative, because Windows is a decent OS
Is a language runtime worth an operating system? I think that's a paradigm we left behind in the 1970s when the two were effectively inseperable (and interwoven with hardware!) I wouldn't expect someone to swap to using a Unix system because they really want a better Haskell experience.
I just don't see any actual interesting or meaningful reasons to care about .NET, I effectively feel the same way about it that I do about Go. Just not something that solves any problem I have, and doesn't have anything that interests me. Although effectively I did try it, so it's a moot point considering that's one of the outcomes you're wishing for.
sakopov an hour ago
Why is the ecosystem bad? I haven't ran any .net code on anything but Linux in years. The open source community is great. I don't know why it gets a bad rep.
throwuxiytayq 5 hours ago
It's a very nice language embedded in a very nice ecosystem. There is no catch, really.
npodbielski 4 hours ago
C# can be used inside Unity game engine. Does this makes it trapped?