[Discussion]: Unions #9663
Replies: 27 comments 129 replies
-
*existing, I assume |
Beta Was this translation helpful? Give feedback.
-
|
What is the purpose for having the |
Beta Was this translation helpful? Give feedback.
-
|
Does exhautiveness work for "partial" unions (a "sub-union")? A related question, can we return a sub-union? |
Beta Was this translation helpful? Give feedback.
-
|
for my opinion add static multi-inheritance for struct other cases we can resolve with [Offsets] |
Beta Was this translation helpful? Give feedback.
-
|
What about this syntax, since C# has recently added the keyword union Pet allows Cat, Dog, Bird;
closed record Pet2 allows Cat, Dog, Bird;I feel no conflict having commas once the parens are gone, interestingly. |
Beta Was this translation helpful? Give feedback.
-
|
Isn't it possible to make a tagged union implementation behind the scenes when all of the cases are struct types, and maintain the same surface area? Basically, |
Beta Was this translation helpful? Give feedback.
-
|
It feels like the non-boxing pattern which is proposed here, is also requiring a "sequence of type tests", calling the TryGetValue methods one-by-one. I wonder if in a future revision of the feature, it would be possible to have an O(1), non-boxing dispatch, by using a switch over System.Type (#356). Switching over System.Type was proposed some time ago but I don't think was ever implemented in the language or runtime. struct MyUnion : IUnion, IUnion<int>, IUnion<bool> { ... }
var myUnion = new MyUnion(true);
switch (myUnion)
{
case int i: ...;
case bool b: ...;
}
// lowered to?
switch (myUnion.GetValueType())
{
case typeof(int): int i = myUnion.IntValue; ...;
case typeof(bool): bool b = myUnion.BoolValue; ...;
}I am just not sure how |
Beta Was this translation helpful? Give feedback.
-
|
You could do exact type switch and then on failure fallback to slower type match. |
Beta Was this translation helpful? Give feedback.
-
Compatibility of proposed union syntax with potential future anonymous unionsCurrently, anonymous unions are out of scope. Is there any possibility they might be added in the future? If not, please disregard this post. Anonymous unions would be useful for method return types and method input parameters. Current proposed union syntax is
Omitting And for other syntaxes discusses in LDM-2025-09-29, using short syntax without the Syntaxes in which the type list is not surrounded by any kind of delimiter don’t seem to work well with anonymous unions, so I’ve added only one example. To me, the easiest to read is |
Beta Was this translation helpful? Give feedback.
-
|
My team are interested in this feature - working in a Typescript frontends and C# backends company there's significant difficulty being proficient in both languages especially when they have features that look similar but are different. From reading the proposal and discussion, and watching the announcement video, I understand this will be one of those features. I've got a few concerns that I'm wondering how or if they will be addressed.
Our team was interested in trying this approach, and used the OneOf library to get used to the idea of unions in C# for this purpose. There's real benefit (actually knowing, with type safety, all the possible response types in our minimal api definitions!) but the ergonomics are painful when you pass unions through layers of services. If
You know this wouldn't compile since I may be misreading the proposal but using the pattern matching switch is the only way to deal with union possibilities, and with that, you cannot perform an early return. OneOf provides a Switch method - but it suffers the same inability to early return due to using lambdas. Your stuck with successively peeling off one value at a time - which explodes the amount of code needed and sets off all kinds of code quality alarms. Is there any way in the given proposal to ergonomically change the control flow based on the value in a union? The most ideal would be something like:
The simplest approach I see is syntax like: or But that implies that either
I know that it's maybe out of scope, or maybe this is something best left up to code gen wizards as an aftermarket thing, but it would be great if there was something typesafe and ergonomic. I know that implicitly casting is off the table as per the spec but maybe explicit doesn't have to be so bad either. Maybe even just a naive "if the target union has an explicitly defined case for the cast union cases and the cast union cases are all explicitly defined, then the cast is valid, otherwise the compiler throws an error. |
Beta Was this translation helpful? Give feedback.
-
|
I haven't seen any mention of F# discriminated unions in any of the LDM notes or discussions. Would there be any compatibility or interoperability? How do the implementations compare? |
Beta Was this translation helpful? Give feedback.
-
|
I am not so sure but it seem I didn't see generic union in the proposal? I think the most common usage of union would be anonymous union of 2-3 types, which I feel almost all people would not want to create explicit type. I bet people would use union in similar manner as delegate, most people just use It would be most beneficial if we can express union like tuple such as or even And it should be non boxing struct with layout fieldoffset 1 (0 for type switch) |
Beta Was this translation helpful? Give feedback.
-
|
Hi, Interesting possible use case for enum extension via #9955 public closed enum BasicColor
{
Red,
Green,
Blue
}
public closed enum ExtendedColor
{
White,
Black
}
public union AllColor(BasicColor, ExtendedColor);My follow up question would be would this work as expected and what would the switch look like? var myColor = AllColor(BasicColor.Green);
var colorName = myColor switch
{
BasicColor.Red => "red",
BasicColor.Green => "green",
ExtendedColor.White => "white"
// Appropriate warnings if all combinations of all enum values are not provided unless _ used?
} |
Beta Was this translation helpful? Give feedback.
-
|
From reading the current version of the proposal, something isn't clear to me: does this require new runtime features? The reason I'm asking this question is because the lack of unions as a first class concept has been lacking in the context of deserialization of discriminated unions. And to help plan code-generation support (in TypeSpec but also in other places), it be great to have a clear idea whether this new feature will require a runtime of net11+, or whether a net11 SDK will be able to "compile it down" to earlier versions of dotnet? The motivation for that question being: if that requires a compatibility bar of net11+ for the runtime, it'll be difficult to adopt in generic libraries which want to have a broad compatibility story. |
Beta Was this translation helpful? Give feedback.
-
|
Hi, I built a small experimental source-generator prototype for union-like types in C# and thought some of the lessons might be relevant here: A few takeaways from implementing it:
|
Beta Was this translation helpful? Give feedback.
-
|
I came to register my very strong preference for ad-hoc union types over nominal discriminated unions... but after looking over the spec, I'm a little more ambivalent. I really like the ad-hoc unions in languages such as TypeScript, and I find the closed unions in languages such as Haskell to be unnecessarily inconvenient. This proposal tries to chart a middle path, by requiring a nominal type to represent the union but also allowing (1) implicit conversion to a union and (2) implicit unwrapping ( The main thing I didn't see in the proposal was cross-union conversions. A common case is that one component can return static AorBorC? Example(BorCorD x) => x is D _ ? null : x; |
Beta Was this translation helpful? Give feedback.
-
|
Hey there very interesting feature indeed. However with the current implementation it is kind of unclear how this could ever be implemented for ref structs, they can't be boxed. So I would assume ref structs are not and will never be supported for these types of unions? |
Beta Was this translation helpful? Give feedback.
-
|
Is there an implicit conversion from union(A, B) to union(A, B, C)? From union(A,C) to union(A,B,C)? |
Beta Was this translation helpful? Give feedback.
-
public interface IHasCommonProperty
{
int CommonProperty { get; set; }
}
public class A : IHasCommonProperty
{
public int CommonProperty { get; set; }
}
public class B : IHasCommonProperty
{
public int CommonProperty { get; set; }
}
public union AorB(A, B);
AorB something = ...;
Console.WriteLine(something.CommonProperty); // Will access to `CommonProperty` work? or access to any other common member that is defined in common interface? |
Beta Was this translation helpful? Give feedback.
-
|
The boxing problem feels like it's going to make the use case for this restricted. I'm not going to want to have to pay the price for boxing to use this with value types unless I can't find any other solution. |
Beta Was this translation helpful? Give feedback.
-
|
I wonder will it be supported in |
Beta Was this translation helpful? Give feedback.
-
|
Today while investigating adding support for custom union types to Polly's Making an existing type a custom union type breaks existing pattern matchingOriginally reported in dotnet/roslyn#83055. Making an existing type a custom union breaks any existing pattern matching on the public members of the type that's been made a union. You can see examples of the changes required to fix the code in the linked PR around the To fix the code, the pattern matching needs to be re-written to work against unions. Custom union type does not work with "is" when used with NullableOriginally reported in dotnet/roslyn#83050. Existing code using There were no compiler warnings for the change, the code just silently stopped working correctly. It was only the library's test suite that found the silent change in semantics. To fix the code, pattern matching needs to be replaced with Outcome<T>? maybeOutcome = Something();
if (maybeOutcome is Outcome<T> outcome)
{
return outcome;
}Fixing the code paths required changing the code to patterns like: if (maybeOutcome.HasValue)
{
return maybeOutcome.GetValueOrDefault();
} |
Beta Was this translation helpful? Give feedback.
-
|
Union "declarations" are implemented with boxing. Is it safe to switch to a non-boxing implementation in the future? |
Beta Was this translation helpful? Give feedback.
-
|
I like the proposal of allowing to switch over, and use custom pattern matching on, user defined types. 1. Value propertyI would not want to have a The object backing field seems like an implementation detail of the default boiler plate code for the implementation of this union feature. Why not just keep it as a field and also generate the TryGetValue methods? All the rules of the Well-formedness seem overcoplicated, when the TryGetValue methods are typesafe and easy to explain. 2. NullabilityThere are in this proposal more than one ways to have a union in the
uninitializedFor me it would make more sense that unions should always be initialized with the same type of compiler checks as non-nullable reference types. If the type is not initialized, it's either undefined behavior or some default state depending on the type, like is the case for any struct you create on a array without initializing it. If the user wants to not initialize the, he can just wrap the union in a nullable and have all the same behavior (unwrapping, automatic casts, HasValue, patern matching), but without the type having to have a HasValue on the enum that most implementation would just point to true. It seems more idiomatic C#. null valueThe null value is a separate state of the union, not a value of any of the type cases on the union. For me that feels weird. If I put in a string?, i'd expect to get a string? back, not that the union is in a separate state with no clue of the string. That some types are allowed to be null, but there is no way to cast back to that type, seems more limiting than just not allowing any nullable types in the union and that you should wrap the union in a Nullable if you need null state. If storing null by type is required, it still seems easy to save the type that was used for the null: This would break the Well-formedness of the Value property, another reason I don't think it's a good idea to expose the object. It would certainly be unfortunate that if we wrap a Nullable in a union, that we have can only match in int ore null instead of Nullable. It just feels wrong. Union member providersThat the compiler searches for an interface this way does not feel like idiomatic C#. union keyword and generatorI'm a bit disappointed with another keyword for a boilerplate generator. It's the same type of feature as the record, that it is: a code generator to produce boiler plate code. For the C# community it would have been a lot better if these features followed a pattern that could be used for regular code generators and that any missing features for doing it that way got implemented That way the community could also write better code generators for boiler plate code. It also adds yet another keyword to C# instead of using already defined flows. I'd even say that requiring the class to be partial and auto generating it on the inclusion of a Newcomers to C# already are overwhelmed by the amount of different flows and keywords in a modern C# library. |
Beta Was this translation helpful? Give feedback.
-
|
For custom, non-boxing implementations, are there performance implications of the TryGet pattern? I assume it will compile down to
As opposed to something like:
|
Beta Was this translation helpful? Give feedback.
-
|
Is there a reason why the
|
Beta Was this translation helpful? Give feedback.
-
|
Does this proposal account for |
Beta Was this translation helpful? Give feedback.

Uh oh!
There was an error while loading. Please reload this page.
-
Several union proposals were consolidated into one. Here is a place to discuss the one.
Proposal document:
https://github.com/dotnet/csharplang/blob/main/proposals/unions.md
Issue for proposal:
#9662
Beta Was this translation helpful? Give feedback.
All reactions