# C# Language Design Meeting for March 11th, 2026

## Agenda

- [Target-typed static member access](#target-typed-static-member-access)
    - [Overview and smorgasbord of parts](#overview-and-smorgasbord-of-parts)
    - [Factory containers](#factory-containers)
    - [Ambiguity with parenthesized expression](#ambiguity-with-parenthesized-expression)
    - [Ambiguity with conditional expression](#ambiguity-with-conditional-expression)

## Quote(s) of the Day

- "I had a thought over the break" "Just the one?" "I'm very single-minded sometimes"
- "We've invented the `Color`/`Colors` problem"

## Discussion

### Target-typed static member access

Champion issue: https://github.com/dotnet/csharplang/issues/9138  
Spec: https://github.com/dotnet/csharplang/blob/c2465a0605180e9624ee5ea9d6e0eab7e93a7c5b/proposals/target-typed-static-member-access.md

Today we came back to the target-typed static member access proposal for a thorough review of its various parts and a new section on
factory containers.

#### Overview and smorgasbord of parts

The proposal comes with a number of separable parts, and we walked through them to gauge appetite for each piece and
determine what to prioritize:

1. **Basic expression**: The core of the proposal. A target-typed member binding expression (`.Xyz`) resolves static fields,
   properties, and enum values on the target type. The conversion always exists so that more useful errors can be produced when
   a member is not found or returns the wrong type. This part is considered essential regardless of what else is included.

2. **Pattern matching**: Enables `.Case` in patterns, allowing much shorter consumption of nested derived types and enum members.
   This is especially valuable for types with long or generic names where redundancy is most painful.

3. **Target-typing with overloadable operators**: Enables the target type to flow through binary and unary overloadable operators,
   so that expressions like `.Public | .Static | .DeclaredOnly` can work. The proposal defines new conversions for unary, binary,
   and cross-operand target-typing. A question was raised about whether the operator conversions should be limited to target-typed
   member binding expressions specifically, or extended to any target-typed expression. The proposal as written would allow any
   target-typed expression (including `new()`) in operator positions, which would make this a bigger change. This remains an open
   question.

4. **Target-typing with invocations**: Enables calling static factory methods via `.Some(42)`, where the invocation is resolved
   through the target type. This requires its own conversion to see through the invocation to find the target type.

5. **Target-typing after `new`**: Enables `new .Success(42)` to look up nested derived types through the target type. This
   provides production/consumption symmetry with pattern matching.

We discussed the approach to reviewing these parts. Rather than trying to keep everything in mind and doing one massive round
of votes at the end, the decision was to proceed incrementally through the parts, acknowledging the risk that a later part could
make us wish an earlier decision had been different, but considering that the lesser evil compared to not making progress.

#### Factory containers

The newest and most discussed part of the proposal addresses a common .NET pattern: when a type is generic, its static factory
methods are often placed on a non-generic sibling type with the same name (e.g., `Task.FromResult<T>` for `Task<T>`,
`ImmutableArray.Create<T>` for `ImmutableArray<T>`). This pattern exists because the language does not support inferring type
parameters from the containing type, only from the method. The SDK rule `CA1000` steers developers in this direction.

Without factory container support, this creates an asymmetry: if `Option<T>` follows the recommended pattern and puts its `Some`
factory method on the non-generic `Option` class, then `.None` would work (since it is on `Option<T>` directly) but `.Some(42)`
would not (since it looks on `Option<T>` and does not find it there).

The proposal addresses this with two mechanisms:

1. **Automatic name matching**: When a target-typed member binding does not find a member on the target type, it automatically
   falls back to looking on a non-generic sibling type with the same fully qualified name. This covers the core BCL patterns like
   `Task`/`Task<T>`, `Option`/`Option<T>`, etc.

2. **An opt-in attribute (`FactoryContainerAttribute`)**: Allows a type to point to a factory container with a different name.
   For example, `IEnumerable<T>` could point to `Enumerable`, or the WPF `Color` struct could point to the `Colors` class.

We had a lively discussion about how far to take factory containers. Several points emerged:

- The generic/non-generic name matching was seen as natural and well-motivated. These pairings (e.g., `Task` and `Task<T>`) are
  perceived by most developers as a cohesive unit, and the fact that they are separate types is a consequence of earlier language
  limitations around type inference.

- There was concern about the attribute-based approach, particularly the more exotic scenarios like pointing `IEqualityComparer<T>`
  at `StringComparer`. Several members felt this was "too cute" and that the error experience would be confusing: if you target
  `IEqualityComparer<int>` and write `.OrdinalIgnoreCase`, you would get a "member not found" error rather than a type mismatch
  error, which could be misleading.

- It was pointed out that `using static` directives and extension methods already serve as escape hatches for these edge cases,
  even if they are imperfect. The hole addressed by the attribute for non-matching names is smaller than the one addressed by the
  core feature.

- The relationship between this feature and target-typed type inference proposals was noted. Some scenarios (like a generic `None`
  method on a non-generic factory type) would benefit from both features working together, where target-typed member access finds
  the factory method and target-typed type inference infers the type argument.

- A point was raised about the consistency between explicit and target-typed access: if `.Red` resolves to `Colors.Red` via an
  attribute on `Color`, then writing `Color.Red` explicitly would fail, which could be confusing. The proposal does prioritize
  direct lookup first, so if `Color.Red` exists it would be preferred, but if it does not exist, the difference between the
  explicit and abbreviated forms could feel strange.

We conducted two reads of the room on factory containers:

1. **Generic/non-generic name matching** (automatic lookup on a non-generic sibling type with the same name): This received
   positive support. We will continue pursuing this as part of the proposal.

2. **The factory container attribute** (opt-in pointing to differently-named factory types): This was more evenly split, with
   leanings both ways. Several members expressed that they could go either way but leaned toward not pursuing the attribute for
   now, treating it as something to keep in the back pocket for later if evidence emerges that it would meaningfully improve
   developers' lives. The attribute adds complexity and the scenarios it addresses can largely be worked around with existing
   language features.

##### Conclusion

Generic/non-generic name matching is doable. We will keep the attribute in our back pocket for now, choosing not to pursue
it in the initial version.

#### Ambiguity with parenthesized expression

The proposal identifies an ambiguity: `(A).B` could be parsed as either dotting off a parenthesized expression (the existing
meaning) or as a cast of a target-typed member binding expression. The recommendation was to preserve the existing meaning only,
consistent with the approach taken for a similar ambiguity in collection expressions where `(expr)[index]` was preserved as
indexing rather than reinterpreted as a cast of a collection expression.

We agreed with this reasoning. There is no practical benefit to allowing the cast interpretation, since `A.B` is already a
simpler way to write the same thing. Keeping the existing meaning avoids breaking changes and is consistent with precedent.

##### Conclusion

Accepted the recommendation. `(A).B` will continue to mean what it has always meant (member access on a parenthesized expression).
The cast interpretation will not be allowed.

#### Ambiguity with conditional expression

There is an ambiguity when target-typed static member access appears in a conditional expression: `a ? .B ? .C : d` could be parsed
as either null-conditional member access (`a?.B`) followed by `? .C : d`, or as a conditional expression with `.B?.C` as the first branch.

The recommendation was to follow the same approach used for the analogous collection expression ambiguity (`expr ? [...]`): look
ahead for the `:` to determine whether this is a conditional expression. This avoids requiring parentheses around the target-typed
expression, which would be a usability burden.

##### Conclusion

Accepted the recommendation. The ambiguity will be resolved the same way as collection expressions, by looking ahead for the `:` to
determine whether it is a conditional expression.
