# C# Language Design Meeting for May 18th, 2026

## Agenda

- [Closed hierarchies open questions](#closed-hierarchies-open-questions)
    - [Exhaustiveness when no subtypes exist](#exhaustiveness-when-no-subtypes-exist)
    - [Matching the base type after all subtypes](#matching-the-base-type-after-all-subtypes)
    - [Type parameters constrained to closed types](#type-parameters-constrained-to-closed-types)
    - [Constraint-based ruling out of generic subtypes](#constraint-based-ruling-out-of-generic-subtypes)

## Quote of the Day

- "This one hopefully... well, don't say how easy you think it's going to be" _General laughter_

## Discussion

### Closed hierarchies open questions

Champion issue: https://github.com/dotnet/csharplang/issues/9499  
Spec: https://github.com/dotnet/csharplang/blob/3613750c73dd2229f814decb5b5911858f6859f2/proposals/closed-hierarchies.md

We returned to `closed` hierarchies to settle a few remaining exhaustiveness questions found during implementation.
The initial implementation has now been merged, with the goal of getting the feature into .NET Preview 5, so the focus
was on confirming whether the current behavior matches the design we want to ship.

The common theme was how much extra precision to add for rare edge cases. In most cases we preferred the regular
exhaustiveness model, consistency with existing pattern matching and conversion behavior, and avoiding special cases
that would be hard to explain or implement for little practical payoff.

#### Exhaustiveness when no subtypes exist

Question: https://github.com/dotnet/csharplang/blob/3613750c73dd2229f814decb5b5911858f6859f2/proposals/closed-hierarchies.md#exhaustiveness-when-no-subtypes-exist

We first considered the degenerate case of a `closed` type with no direct subtypes. In a purely set-theoretic sense,
an empty switch over such a type has handled all zero known subtypes. That regularity is appealing, and it is possible
to imagine generated code benefiting from treating an empty hierarchy exactly like any other closed hierarchy.

However, the practical signal is different. A `closed` type with no subtypes is likely to be an intermediate state
while the author is still writing the hierarchy, and a switch over an input of that type strongly suggests that the
programmer expects an instance to be possible. Treating the empty switch as exhaustive would therefore rely on the
opposite assumption: that no instance can ever flow to the switch. We found that tension more important than the
mathematical regularity.

The implementation already treats this case as non-exhaustive, essentially falling back to the ordinary behavior for a
base class. That means an empty switch reports a warning, and users can satisfy exhaustiveness by matching the base
type itself. We were comfortable with this conservative behavior, especially because relaxing a warning later would not
be a breaking change if compelling scenarios appear.

##### Conclusion

When a `closed` type has no subtypes, an empty switch over that type is not considered exhaustive. The compiler will
report an exhaustiveness warning unless the switch handles the base type itself.

#### Matching the base type after all subtypes

Question: https://github.com/dotnet/csharplang/blob/3613750c73dd2229f814decb5b5911858f6859f2/proposals/closed-hierarchies.md#allow-matching-the-base-type-after-matching-all-subtypes

We then revisited whether a switch should be allowed to match the `closed` base type after it has already matched all
possible subtypes. Under the normal exhaustiveness model, that arm is unreachable: once all possible runtime types have
been handled, there is no remaining non-null value that can match the base type. The question was whether we should
make an exception because the error can appear when a consumer upgrades to a language version or library version that
understands the hierarchy as closed.

We recognized the upgrade concern. In an older language version, a consumer may have needed a base-type arm to make the
switch exhaustive. After the hierarchy becomes known as closed, that same arm can become unreachable. That can feel
harsh, particularly for multi-targeting or dependency-upgrade scenarios.

Even so, we did not find a better general rule. The same reachability machinery is what lets exhaustiveness produce
useful errors, and special-casing this scenario would require the compiler to use different rules for deciding whether
the switch is exhaustive and whether an individual arm is reachable. That would be difficult to justify and harder to
explain. We also want the compile-time break when a closed hierarchy changes to remain useful: if a dependency adds a
new subtype, many users want the compiler to point out the switches that need to be revisited.

There is also an explicit escape hatch for unusual scenarios. If a user genuinely wants to ignore the closed-hierarchy
knowledge, they can cast the switch input to `object` before switching. The compiler then no longer narrows according
to the closed type, and the user can write a catch-all pattern in the ordinary way.

##### Conclusion

We will continue to report an error when a switch matches a `closed` base type after all of its possible subtypes have
already been matched. Users who need to opt out of the closed-hierarchy reasoning can cast the switch input to
`object`.

#### Type parameters constrained to closed types

Question: https://github.com/dotnet/csharplang/blob/3613750c73dd2229f814decb5b5911858f6859f2/proposals/closed-hierarchies.md#exhaustiveness-of-type-parameters-constrained-to-closed-type

Next we discussed switches where the input type is not the `closed` type directly, but a type parameter constrained to
that closed type. For example, if `X` is constrained with `where X : C`, should a switch over an `X` be considered
exhaustive after it handles all direct subtypes of `C`?

We think it should. The effective base type of the type parameter gives us the same closed-hierarchy information that
we would have if the input type were `C` itself. If every possible value of `X` must also be a value in the closed
hierarchy rooted at `C`, then matching the possible subtypes of `C` should exhaust the input. We think users will expect
that behavior, and it would be surprising if this case warned while the direct `C` case did not.

This also means the corresponding reachability behavior should line up. Once all relevant subtypes have been handled,
a later base-type arm should be treated as unreachable in the same way it would be for a switch whose input type is the
closed type itself. The implementation had initially deferred this work to get the basic feature in, but we agreed that
supporting it is the right model.

##### Conclusion

We will support closed-hierarchy exhaustiveness for type parameters whose effective base type is a `closed` type. The
behavior should match the behavior for switching directly on the closed type, including the corresponding unreachable
arm diagnostics.

#### Constraint-based ruling out of generic subtypes

Question: https://github.com/dotnet/csharplang/blob/3613750c73dd2229f814decb5b5911858f6859f2/proposals/closed-hierarchies.md#ruling-out-generic-closed-subtypes-based-on-constraints

Finally, we looked at whether exhaustiveness should use generic constraints to prove that a generic subtype is
impossible. The motivating example has a closed generic base type and a generic subtype with a `struct` constraint,
while the switch input is constrained in a way that only permits class type arguments. From a human perspective, those
constraints may make the subtype impossible, and therefore one might expect the switch to be exhaustive without a
base-type arm.

We decided not to add that analysis. The compiler already needs to rule out generic subtypes in cases where the types
cannot unify at all, such as a subtype of `C<V[]>` when switching over `C<string>`. That kind of unification is part of
the existing shape of the feature. Adding constraint reasoning would be a different level of precision: the compiler
would need to understand when sets of constraints are mutually exclusive across all the relevant type parameters and
constraint forms.

That extra precision does not seem worth the cost for such rare code. In practice, the fallback is to require a
base-type arm even if the programmer can reason that the arm will never match. This is a little less precise, but it is
consistent with other C# behavior that asks whether types can unify without also considering all constraint
compatibility. We would rather keep the rule simple and revisit only if users bring concrete scenarios where the
additional precision matters.

##### Conclusion

We will not use generic constraints to rule out otherwise-unifiable closed subtypes. If this causes users to need an
apparently unnecessary base-type arm in complex constraint scenarios, we can consider improvements based on feedback.
