# C# Language Design Meeting for February 4th, 2026

## Agenda

- [Discriminated unions patterns](#discriminated-unions-patterns)
    - [Null ambiguity in constructor selection](#null-ambiguity-in-constructor-selection)
    - [Marking unions with an attribute instead of IUnion interface](#marking-unions-with-an-attribute-instead-of-iunion-interface)
    - [Factory method support](#factory-method-support)
    - [Union member providers](#union-member-providers)

## Quote of the Day

- "The distant past. You know, late summer or early fall."

## Discussion

### Discriminated unions patterns

Champion issue: https://github.com/dotnet/csharplang/issues/9662  
Spec: https://github.com/dotnet/csharplang/blob/d614288f7dab369f763520ba48b12623c34d8542/proposals/unions.md#union-declarations  
Working group notes: https://github.com/dotnet/csharplang/blob/d614288f7dab369f763520ba48b12623c34d8542/meetings/working-groups/discriminated-unions/union-patterns-update.md

Today, we went over a few open questions in unions.

#### Null ambiguity in constructor selection

When a union has multiple reference type case types, passing `null` creates ambiguity about which constructor to
use. For example, with `Pet(Dog)` and `Pet(Bird?)` constructors, `null` could apply to either.

Currently, the compiler picks an arbitrary applicable constructor when there is ambiguity, based on the assumption
that well-formed unions have constructors that behave identically for the same input. However, this can lead to
surprising behavior: if the compiler picks the `Dog` constructor but `Bird?` is the one intended to accept `null`,
a nullability warning will be issued even though the union author intended `null` to be valid.

We discussed several approaches:
1. Using standard overload resolution betterness rules to select among applicable constructors
2. Requiring the user to be explicit when multiple cases could apply (warning or error on ambiguity)
3. Ensuring nullability annotations factor into the decision

We liked options 1 and 2 here. Option 3 is not how the compiler works today, and doesn't solve the scenario in nullable
disabled locations. There are also scenarios where null isn't involved, such as a union of two interfaces and a type
that implements both. We also brought up operator conversions, as union conversions are very similar to user-defined
operators. We like that analogy, and want to apply similar rules where applicable.

##### Conclusion

The working group will consider requiring explicit disambiguation when ambiguity cannot be resolved through standard
betterness rules, similar to how conversion operators are resolved.

#### Marking unions with an attribute instead of IUnion interface

The current design uses the `IUnion` interface to indicate that a type should be treated as a union. This causes
problems:
- A type cannot implement `IUnion` without becoming a union (it might just want runtime participation)
- Derived types of a union type automatically become unions themselves, which may not be intended and raises
  awkward questions about what their case types should be
- It conflates the runtime interface (useful for generic code that handles unions) with the compiler recognition
  mechanism

The proposal is to use a `[Union]` attribute instead, with the compiler looking for members by shape rather than
through the interface. The `IUnion` and `IUnion<TUnion>` interfaces would still exist for runtime scenarios (such
as generic code that queries whether something is a union or accesses its value), but they would be optional and
not required for compiler recognition.

##### Conclusion

Approved. The design will change from requiring `IUnion` interface implementation to using a `[Union]` attribute
for compiler recognition. Members will be looked up by shape (constructors with case type parameters, a `Value`
property).

#### Factory method support

Constructors have limitations: they cannot be defined on a type other than the one they construct, and they cannot
be inherited or delegated. Static factory methods provide more flexibility for scenarios involving type hierarchies,
object pooling, or delegating union member definitions to a separate type.

The proposal is to allow `public static Create` methods as an alternative to constructors for defining case types.
When factory methods are present, they take precedence over constructors.

There was significant concern about implicit recognition of factory methods based on shape alone. A method like
`Parse(string)` returning the union type could be unintentionally picked up as defining a `string` case type. Even
if we restrict to just a single name (`Create`), we don't have enthusiasm for the magic shape recognition among
different overloads here.

The LDM expressed strong support for requiring explicit markers rather than relying on implicit shape matching.
The proposal of a `[UnionCase]` attribute was well-received: authors would mark constructors, factory methods, or
even user-defined implicit conversion operators that should contribute to the union's case types.

A follow-up question arose: should the `[UnionCase]` attribute be required on everything, including constructors,
or should there be a simple default mode where constructors work implicitly? Two positions emerged:

1. Attributes on everything: Any member used by the compiler for union behavior must be marked with `[UnionCase]`.

2. Default behavior for constructors: If a union type has only constructors (no factory methods or conversion
   operators), they work implicitly without attributes. Once you want any customization (factory methods, conversion
   operators, or selective inclusion of constructors), you must use `[UnionCase]` on all participating members.

A read-of-the-room vote showed preference for the second approach: allow the simple constructor-only case to work
without attributes, while requiring explicit marking for any advanced scenarios. However, we want the working group
to take a look at the implications and come back with a concrete proposal and recommendations.

##### Conclusion

Move forward with the `[UnionCase]` attribute approach. The working group will produce a concrete proposal with
specific rules. There is no requirement for a specific factory method name when the attribute is used.

#### Union member providers

This proposal addresses scenarios where the union's desired behavior conflicts with its public surface area. Two
cases were identified:
1. The type has members that would be incorrectly recognized as union members
2. The type needs union members but does not want them in its public API

The idea was to allow delegating union member lookup to a separate "provider" type (e.g., a nested interface).

Given the decision to use explicit `[UnionCase]` attributes, the first scenario is largely addressed: members won't
be picked up unless explicitly marked. For the second scenario, user-defined implicit conversion operators (if
supported) could address types like `Result<T>` that expose conversions but not constructors. However, we don't have
good examples of what could actually need this ability. Given that, we will simply table this addition for now, and
come back when we have concrete scenarios to address.

##### Conclusion

This feature will be saved for later. The explicit attribute approach addresses the primary motivating scenarios.
If compelling use cases emerge that require full delegation to a provider type, the design space remains open.

### General principle established

Throughout the discussion, a general principle emerged: the LDM is comfortable with some implicit behavior for
well-known members (like recognizing a property named `Value`), but prefers explicit marking for case type
definitions due to the inherent variability and potential for false positives. It's possible further consideration
of real use cases may cause us to want to go even further into the explicit territory (explicitly marking `Value` or
the `TryValue` methods, for example), but the working group will explore this space.
