# C# Language Design Meeting for June 8th, 2026

## Agenda

- [Dictionary expressions](#dictionary-expressions)
    - [IReadOnlyDictionary comparer binding](#ireadonlydictionary-comparer-binding)
    - [KeyValuePair variance in params](#keyvaluepair-variance-in-params)
- [Unions](#unions)
    - [Union pattern target for custom unions](#union-pattern-target-for-custom-unions)
- [Labeled break and continue](#labeled-break-and-continue)

## Quote of the Day

- "Okay, I hate it. Thanks."

## Discussion

### Dictionary expressions

Champion issue: https://github.com/dotnet/csharplang/issues/8659

#### IReadOnlyDictionary comparer binding

Spec: https://github.com/dotnet/csharplang/blob/ffabcf2ebd738c759d1d80dcfbd20645040fde2f/proposals/dictionary-expressions.md#for-ireadonlydictionaryk-v-what-symbol-should-withcomparer--bind-to

We started by revisiting a question that was left open from collection expression arguments: when a dictionary expression
targets `IReadOnlyDictionary<TKey, TValue>` and uses `with` to provide an `IEqualityComparer<TKey>`, what symbol should the
compiler expose for that construction?

For mutable dictionary targets, the answer is straightforward because the implementation uses `Dictionary<TKey, TValue>`
and can expose the corresponding concrete constructors. For read-only dictionary targets, however, the runtime value is not
itself intended to be a mutable dictionary. The current implementation can create a dictionary and wrap it in a read-only
wrapper, and future implementations might use other representations such as specialized or frozen dictionaries. That makes
it awkward to say that the expression constructs a mutable dictionary, even if dictionary construction provides the most
useful mental model for comparer semantics.

We considered whether to expose the concrete `Dictionary<TKey, TValue>(IEqualityComparer<TKey>)` constructor, synthesize a
new compiler-only symbol, or find some other runtime type that represented the operation more accurately. A synthesized
symbol would avoid showing potentially misleading XML documentation from `Dictionary<TKey, TValue>`, but it would add
compiler and IDE work and would still require tooling to invent useful documentation and signature help. The group also
noted that this is primarily a tooling and Roslyn API question rather than a question that changes the runtime semantics of
the resulting program.

The practical argument for the dictionary constructor was strong: it is much cheaper to implement, gives users a familiar
signature, and describes the semantics closely enough as long as the spec is clear that the implementation is free to wrap
or replace the dictionary representation.

##### Conclusion

For `IReadOnlyDictionary<TKey, TValue>` targets with a comparer argument, we will bind to the
`Dictionary<TKey, TValue>(IEqualityComparer<TKey>)` constructor symbol. The spec should make clear that the implementation
is not required to expose or return a mutable `Dictionary<TKey, TValue>` instance.

#### KeyValuePair variance in params

Spec: https://github.com/dotnet/csharplang/blob/ffabcf2ebd738c759d1d80dcfbd20645040fde2f/proposals/dictionary-expressions.md#support-keyvaluepair-variance-with-params

We then discussed whether the special `KeyValuePair<TKey, TValue>` conversions used by dictionary expressions should also
apply to expanded `params` calls. Dictionary expressions can convert individual key and value positions, so a collection
expression can contain a `KeyValuePair<string, int>` where the target element type is `KeyValuePair<object, int?>`. There is
not, however, a general language conversion from `KeyValuePair<string, int>` to `KeyValuePair<object, int?>` outside the
dictionary expression context.

The motivating question was whether a `params KeyValuePair<object, int?>[]` call in expanded form should behave like the
bracketed collection expression form. We generally like `params` calls and collection expression calls to be close to
interchangeable, but applying these conversions to expanded `params` calls would make ordinary parameter conversion rules
depend on a feature-specific `KeyValuePair<,>` rule. That would create a new inconsistency: the single-parameter call would
still fail, while the corresponding expanded `params` call would succeed.

We also revisited the historical reason for keeping the `KeyValuePair<,>` behavior localized. Dictionary expressions were
designed around the existing .NET dictionary ecosystem, where `KeyValuePair<,>` is central, but we intentionally avoided
turning it into a general tuple-like variant conversion throughout the language. Extending that behavior into `params`
would reopen those broader conversion and overload-resolution concerns for a scenario that appears rare.

There is also an easy workaround: callers who want dictionary-expression conversion behavior can use the bracketed
collection expression form explicitly.

##### Conclusion

We will not support `KeyValuePair<,>` variance for expanded `params` calls. The special conversion remains limited to
dictionary expression contexts.

### Unions

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

#### Union pattern target for custom unions

We returned to the union pattern matching question from our
[previous discussion](./LDM-2026-05-20.md#union-pattern-matching-target-for-custom-unions). The issue is how pattern
matching should behave when an existing union-like type adopts the union pattern by adding `UnionAttribute`. If all
non-trivial patterns are redirected to the union's `Value` property, existing code that matched against properties or
positional members of the wrapper type can silently stop matching as it did before.

The working group had narrowed the options but had not reached a conclusion. One option was to keep the current behavior,
where most patterns apply to `Value`. A second option was a syntactic rule: patterns that do not contain type, null, or
constant checks apply to the union wrapper itself, while patterns with those checks unwrap to the value. A third option was
the "try both" model: first try matching against the union instance, and if that does not match, try the contained value.

The syntactic rule is easier to inspect locally. Looking at the shape of the pattern is enough to tell whether the wrapper
or the value is the target. The downside is that adding or removing a type from the pattern becomes the control point for
unwrapping, which can feel accidental. It also means type patterns can no longer match the union wrapper itself, even in
cases where the wrapper intentionally implements an interface that users may want to test.

The try-both rule is more subtle, but it has a stronger compatibility story. Broadly, patterns that matched the type before
it became a union continue to match, while patterns that previously failed may now get a second chance against the union's
contents. We discussed examples such as a `OneOrMany<T>` union that might itself implement `IEnumerable<T>`. Under try-both,
an `IEnumerable<T>` pattern would match the wrapper first; under the syntactic rule, the same pattern would reach into the
contents instead. That is a real semantic difference, but it also showed why either rule needs an escape hatch. Users can
explicitly match `.Value` to force unwrapping, and can use an `object`-typed intermediate to force matching the wrapper.

We also checked whether try-both still had unresolved problems around logical patterns. The working group believed these
were largely clarifications rather than blockers: when a pattern branch succeeds, the successfully matched target carries
forward to the rest of the pattern.

##### Conclusion

We will pursue the try-both model, pending feasibility investigation. If try-both cannot be implemented in the required
timeframe, the syntactic rule is the fallback option.

### Labeled break and continue

Champion issue: https://github.com/dotnet/csharplang/issues/9875  
Spec: https://github.com/dotnet/csharplang/blob/ffabcf2ebd738c759d1d80dcfbd20645040fde2f/proposals/labeled-break-continue.md#open-questions

We closed by reviewing the proposed spec for labeled `break` and `continue`. We had previously been positive on the feature
direction, and the goal of this discussion was to check the concrete spec changes and open questions before trying to fit
the implementation into the release.

The core model is that labels remain ordinary labeled statements, but a label immediately applied to a `switch` statement or
iteration statement can be named by a `break` statement, and a label immediately applied to an iteration statement can be
named by a `continue` statement. Without an identifier, `break` and `continue` continue to target the nearest applicable
enclosing statement. With an identifier, the statement targets the nearest applicable enclosing statement labeled with that
identifier. Existing `finally` and control-flow rules continue to apply; the spec does not need special new rules for those
cases.

We discussed multiple labels on the same syntactic statement, such as `a: b: while (...)`. Multiple labels are already legal
C#, but we do not need to make every such label target the same loop for this feature. Under the proposed rule, only the
statement immediately nested inside the labeled statement is labeled by that identifier. In `a: b: while (...)`, the `while`
is labeled by `b`; `a` labels the inner labeled statement `b: while (...)`, not the loop itself. This is a narrow scenario,
and allowing `break a;` or `continue a;` inside the loop can be added later without breaking existing programs if it ever
proves important.

We also discussed whether the spec should model labeled `break` and `continue` as resolving labels exactly like `goto` and
then checking whether the resolved label is an allowed target, or as directly searching for a containing labeled switch or
iteration statement. The accepted programs are intended to be the same either way. Some feedback favored the `goto`-like
formulation as more robust to any future label shadowing model, while the direct formulation was closer to the existing
`break` and `continue` text. The group did not see this as a blocking issue, provided diagnostics remain helpful when a
label exists but is not a valid target for `break` or `continue`.

##### Conclusion

The proposed labeled `break` and `continue` direction is acceptable. We will not add special support for multiple labels
targeting the same loop or switch as part of this feature, and the spec author should incorporate the wording feedback.
