You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Ad-Hoc Union: Allow _valueIndex = 0 as supported state #22
I would like to use the default state of the struct-based union as a valid combination. For my particular example, the struct MaybeOf<T> is the logical value Null when it is "just default" (but the usage of default outside of the struct should still not be possible by using IDisallowDefaultValue).
Implemented and coming with the release 10.5.0-beta02.
There is a new setting DefaultValueHandling on the union attributes. With UnionDefaultValueHandling.MapToFirstMember, the default value of a struct-based ad-hoc union represents the first member (T1) instead of being an invalid state. Your MaybeOf<T> looks like this:
publicreadonlyrecordstructNull;// stateless marker: "no value"[Union<Null,TypeParamRef1>(T1IsStateless=true,T2Name="Value",DefaultValueHandling=UnionDefaultValueHandling.MapToFirstMember)]publicreadonlypartialstructMaybeOf<T>;
MaybeOf<string>none=default;// valid: represents the Null stateConsole.WriteLine(none.IsNull);// TrueMaybeOf<string>some=MaybeOf<string>.CreateValue("hello");Console.WriteLine(some.IsNull);// FalseConsole.WriteLine(some.AsValue);// hello
Details:
Internally the discriminator becomes 0-based, so default(MaybeOf<T>), new MaybeOf<T>() and a constructed Null instance are physically identical: equality, GetHashCode, IsNull, Switch, Map and ToString all work without a special case. Nothing throws anymore for the default value.
Constraints: the union must be a struct (TTRESG081) and T1 must be stateless via T1IsStateless = true (TTRESG082). Only T1 can be the default state; if another member should represent "no value", reorder the type parameters.
Because a type parameter is involved, factory methods are generated for all members: MaybeOf<T>.CreateNull() and MaybeOf<T>.CreateValue(...). A static property like None is not generated; add it yourself if you prefer that style: public static MaybeOf<T> None => default;
An implicit conversion from T is not generated for type-parameter members, because a user-defined conversion from a type parameter breaks when T is instantiated with an interface or object. Use the factory methods instead, or declare the operator manually in your partial part: public static implicit operator MaybeOf<T>(T? value) => value is null ? None : CreateValue(value); (For non-generic unions like [Union<Null, int>] the implicit conversion from the member type is generated as usual.)
Regarding IDisallowDefaultValue: the interface is now public, so the #pragma warning disable TTRESG1000 workaround is no longer necessary. With MapToFirstMember the generator no longer emits the interface, because the default value is valid and banning it makes no sense by default. If you still want to force consumers to use a named accessor instead of writing default, implement the interface manually; the analyzer then reports TTRESG047 on default/new() again, while the runtime value stays a fully functional Null state:
Activity
feat: add DefaultValueHandling for ad-hoc unions
PawelGerr commentedon Aug 5, 2026
Implemented and coming with the release
10.5.0-beta02.There is a new setting
DefaultValueHandlingon the union attributes. WithUnionDefaultValueHandling.MapToFirstMember, thedefaultvalue of a struct-based ad-hoc union represents the first member (T1) instead of being an invalid state. YourMaybeOf<T>looks like this:Details:
default(MaybeOf<T>),new MaybeOf<T>()and a constructedNullinstance are physically identical: equality,GetHashCode,IsNull,Switch,MapandToStringall work without a special case. Nothing throws anymore for the default value.struct(TTRESG081) and T1 must be stateless viaT1IsStateless = true(TTRESG082). Only T1 can be the default state; if another member should represent "no value", reorder the type parameters.MaybeOf<T>.CreateNull()andMaybeOf<T>.CreateValue(...). A static property likeNoneis not generated; add it yourself if you prefer that style:public static MaybeOf<T> None => default;Tis not generated for type-parameter members, because a user-defined conversion from a type parameter breaks whenTis instantiated with an interface orobject. Use the factory methods instead, or declare the operator manually in your partial part:public static implicit operator MaybeOf<T>(T? value) => value is null ? None : CreateValue(value);(For non-generic unions like[Union<Null, int>]the implicit conversion from the member type is generated as usual.)Regarding
IDisallowDefaultValue: the interface is now public, so the#pragma warning disable TTRESG1000workaround is no longer necessary. WithMapToFirstMemberthe generator no longer emits the interface, because the default value is valid and banning it makes no sense by default. If you still want to force consumers to use a named accessor instead of writingdefault, implement the interface manually; the analyzer then reports TTRESG047 ondefault/new()again, while the runtime value stays a fully functionalNullstate:Documentation: Discriminated Unions › Customization