# Target-typed static member access

Champion issue: <https://github.com/dotnet/csharplang/issues/9138>

Thanks to those who provided insight and input into this proposal, especially @CyrusNajmabadi!

## Summary

This feature enables a type name to be omitted from static member access when it is the same as the target type.

This reduces construction and consumption verbosity for factory methods, nested derived types, enum values, constants, singletons, and other static members.

Besides making existing types easier to use, this feature also opens the door so that when designing discriminated unions, the option is available to lower to nested derived types without compromising ease of construction and consumption.

```cs
type.GetMethod("Name", .Public | .Instance | .DeclaredOnly); // BindingFlags.Public | ...

if (someString.Equals("Value", .OrdinalIgnoreCase)) // StringComparison.OrdinalIgnoreCase
    ...

control.ForeColor = .Red;    // Color.Red
entity.InvoiceDate = .Today; // DateTime.Today

// Production (static members on Option<int>)
Option<int> option = condition ? .None : .Some(42);

// Production (nested derived types)
CustomResult result = condition ? new .Success(42) : new .Error("message");

// Consumption (nested derived types)
return result switch
{
    .Success(var val) => val,
    .Error => defaultVal,
};

[AttributeUsage(.Class | .Struct | .Interface | .Enum | .Delegate)]
class MyAttribute : Attribute;
```

## Motivation

This feature removes painful boilerplate, matches with industry trends, and responds to the community's steady interest in this feature in terms of discussions and upvotes.

Repeating a full type name before each `.` can be redundant. This happens often enough that it would make sense to put the choice in developers' hands to avoid the redundancy. Today, there's no scalable workaround for the verbosity in the following example:

```cs
public void M(Result<ImmutableArray<int>, string> result)
{
    switch (result)
    {
        case Result<ImmutableArray<int>, string>.Success(var array): ...
        case Result<ImmutableArray<int>, string>.Error(var message): ...
    }
}
```

This feature brings a dramatic quality-of-life improvement:

```cs
public void M(Result<ImmutableArray<int>, string> result)
{
    switch (result)
    {
        case .Success(var array): ...
        case .Error(var message): ...
    }
}
```

The industry has already started moving in the same direction. The Swift, Dart, and Zig languages have all added the `.xyz` syntax with the same meaning as this proposal for C#, and Rust has an RFC proposing the same dotted syntax:

- Swift calls it [implicit member expressions](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/expressions#Implicit-Member-Expression).
- Dart calls it "dot shorthands" or [static access shorthand](https://github.com/dart-lang/language/blob/c31243942eacadcc1be8cf81016f758fe831b99c/working/3616%20-%20enum%20value%20shorthand/proposal-simple-lrhn.md).
- Rust has an active RFC which calls it [path inference](https://github.com/rust-lang/rfcs/pull/3444).
- Zig calls it [enum literals](https://ziglang.org/documentation/master/#Enum-Literals).

This feature also puts more options on the table for the design of discriminated unions by improving the ergonomics of nested derived types. This ergonomic improvement is worth doing anyway for the sake of existing types, but it also evens the playing field among the various lowering options for discriminated unions.

## Detailed design

### Basic expression

There is a new primary expression, _target-typed member binding expression_, which starts with `.` and is followed by an identifier: `.Xyz`. What makes it a target-typed member binding, rather than some other kind of member binding, is its location as a primary expression.

If this expression appears in a location where there is no target type, a compiler error is produced. Otherwise, this expression is bound in the same way as though the identifier had been qualified with the target type.

To determine whether there is a target type and what that target type is, the language adds an implicit _target-typed member binding conversion_, from _target-typed member binding expression_ to any type. The conversion succeeds regardless of the target type. This allows errors to be handled in binding after the conversion succeeds, such as not finding an accessible or applicable member with the given identifier, or such as the expression evaluating to an incompatible type. For example:

```cs
SomeTarget a = .A; // Same as SomeTarget.A, succeeds
SomeTarget b = .B; // Same as SomeTarget.B, fails with accessibility error
SomeTarget c = .C; // Same as SomeTarget.C, fails trying to assign `int` to `SomeTarget`
SomeTarget d = .D; // Same as SomeTarget.D, succeeds via implicit conversion
SomeTarget e = .E; // Same as SomeTarget.E, fails to locate a static member
// etc

class SomeTarget
{
    public static SomeTarget A;
    private static SomeTarget B;
    public static int C;
    public static string D;
    public SomeTarget E;

    public static implicit operator SomeTarget(string d) => ...
}
```

The fact that the conversion succeeds regardless of target type also enables [target-typing with invocations](#target-typing-with-invocations).

This is sufficient to allow this new construct to be combined with constructs which allow target-typing. For example:

```cs
SomeTarget a = condition ? .A : .D;
```

If the resulting bound member is a constant, it may be used in locations which require constants, no differently than if it was qualified:

```cs
const BindingFlags f = .Public;
```

### Pattern matching

A core scenario for this proposal is to be able to match nested derived types without repeating the containing type name, which may be long especially with generics.

```cs
public void M(Result<ImmutableArray<int>, string> result)
{
    switch (result)
    {
        case .Success(var array): ...
        case .Error(var message): ...
    }
}
```

This includes nested patterns:

```cs
expr is { A: .Some(0) or .None }
```

Any type expression in a type pattern may begin with a `.`. It is bound as though it was qualified with the type of the expression or pattern being matched against. If such qualified access is permitted, the target-typed type pattern is permitted. If such qualified access is not permitted, the target-typed type pattern fails with the same message.

### Target-typing with overloadable operators

A core scenario for this proposal is using bitwise operators on flags enums. To enable this without adding arbitrary limitations, this proposal enables target-typing on the operands of overloadable operators.

```cs
GetMethod("Name", .Public | .Static)
MyFlags f = ~.None;
if ((myFlags & ~.Mask) != 0) { ... }
```

Other target-typed expressions besides `.Xyz` will be able to benefit from this, such as `null`, `default` and `[]`. One exception to this target-typed `new`, which explicitly states that it may not appear as an operand of a unary or binary operator. If desired, we could lift this restriction so that it is not the odd one out:

```cs
Point p = origin + new(50, 100);
```

There is an [open question](#target-typing-existing-operand-expressions) on whether to lift this restriction and whether to allow other target-typed expressions besides `.Xyz`.

This is done by adding three new conversions: _unary operator target-typing conversion_, _binary operator target-typing conversion_, and _binary cross-operand target-typing conversion_.

For a unary operator expression such as `~e`, we define a new implicit _unary operator target-typing conversion_ that permits an implicit conversion from the unary operator expression to any type `T` for which there is a conversion-from-expression from `e` to `T`.

For a binary operator expression such as `e1 | e2`, we define a new implicit _binary operator target-typing conversion_ that permits an implicit conversion from the binary operator expression to any type `T` for which there is a conversion-from-expression from `e1` to `T` and/or from `e2` to `T`.

For _either operand_ of a binary operator expression such as `e1 | e2`, if one expression has a type `T` and the other expression does not have a type, and there is a conversion-from-expression from the typeless operand expression to `T`, we define a new implicit _binary cross-operand target-typing conversion_ that permits an implicit conversion from the typeless operand expression to `T`.

Target-typing from one operand to another is helpful in the following scenario:

```cs
M(BindingFlags.Public | .Static | .DeclaredOnly); // Succeeds

M(.Public | .Static | .DeclaredOnly); // ERROR: overload resolution fails

void M(BindingFlags p) => ...
void M(string p) => ...
```

### Target-typing with invocations

A core scenario for this proposal is calling factory methods. This enables the use of the feature with some of today's class and struct types.

```cs
SomeResult = .Error("Message");
Option<int> M() => .Some(42);
```

To enable target-typing for the invoked expression within an invocation expression, a new conversion is added, _invocation target-typing conversion_.

For an invocation expression such as `e(...)` where the invoked expression `e` is a _target-typed member binding expression_, we define a new implicit _invocation target-typing conversion_ that permits an implicit conversion from the invocation expression to any type `T` for which there is a _target-typed member binding conversion_ from `e` to `Tₑ`.

Even though the conversion always succeeds when the invoked expression `e` is a  _target-typed member binding expression_, further errors may occur if the invocation expression cannot be bound for any of the same reasons as though the _target-typed member binding expression_ was a non-target-typed expression, qualified as a member of `T`. For instance, the member might not be invocable, or might return a type other than `T`.

### Target-typing after the `new` keyword

A core scenario for this proposal is enable the `new` operator to look up nested derived types. This provides symmetry between production and consumption of values with class DUs and with today's type hierarchies based on nested derived classes or records.

```cs
new .Case1(arg1, arg2)
```

This balances the consumption syntax:

```cs
du switch
{
    .Case1(var arg1, var arg2) => ...
}
```

This would continue to be target-typed static member access (since nested types are members of their containing type), which is distinct from target-typed `new` since a definite type is provided to the `new` operator.

TODO: spec the conversion

### Notes

As with target-typed `new`, targeting a nullable value type should access members on the inner value type:

```cs
Point? p = new();  // Equivalent to: new Point()
Point? p = .Empty; // Equivalent to: Point.Empty
```

As with target-typed `new`, overload resolution is not influenced by the presence of a target-typed static member expression. If overload resolution was influenced, it would become a breaking change to add any new static member to a type.

```cs
M(.Empty); // Overload ambiguity error

void M(string p) { }
void M(object p) { }
```

Finally, extension members are included in the lookup.

### Factory containers

#### Summary (factory containers)

Target-typed static members will be found on separate factory container types, for example:

- `Option<ImmutableArray<int>> result = .Some([42]);` - Calls `Some<T>` on the nongeneric `Option` type
- `SearchValues<char> separators = .Create(',', ';');` - Calls `Create(ReadOnlySpan<char>)` on the nongeneric `SearchValues` type
- `Tensor<T> c = .Add(a, b);` - Calls `Add<T>` on the nongeneric `Tensor` type

This will happen automatically when the factory member is on a nongeneric sibling type with the same name.

#### Motivation (factory containers)

A common .NET pattern for obtaining a value of some generic type is to call a factory method on a nongeneric type of the same name so that the type argument can be inferred. The core libraries follow this pattern. For example:

- `KeyValuePair.Create<TKey, TValue>` to obtain a `KeyValuePair<TKey, TValue>`
- `Task.FromResult<T>` to obtain a `Task<T>`
- `Vector.Add<T>` to obtain a `Vector<T>`, along with many other factory methods for other operations.
- `Tensor.Add<T>` to obtain a `Tensor<T>`, along with many other factory methods for other operations.
- `Vector128.Add<T>` to obtain a `Vector128<T>`, along with many other factory methods for other operations.
- `ImmutableArray.Create<T>` to obtain an `ImmutableArray<T>`
- `Tuple.Create<T1, T2, ...>` to obtain a `Tuple<T1, T2, ...>`
- `Channel.CreateBounded<T>` to obtain a `Channel<T>`
- `SearchValues.Create(ReadOnlySpan<byte>)` to obtain a `SearchValues<byte>`, and overloads for `<char>` and `<string>`
- `Expression.Lambda<TDelegate>` to obtain an `Expression<TDelegate>`

This pattern has wide uptake in community APIs as well. The SDK steers users in this direction with the [CA1000: Do not declare static members on generic types](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1000) rule.

Following these patterns and warnings, existing `Option<T>` types should put their `.Some` factory method on a nongeneric class, `Option.Some<T>`. This enables generic type inference, so `Option.Some(42)` can be written.

However, this means that the method `Option.Some<T>` does not exist. This would cause an odd asymmetry: in the core proposal, you'd be able to write `.None`, but not `.Some(val)`. The `None` member is on `Option<T>` because there are no type inference opportunities, in the same manner as `ImmutableArray<T>.Empty`. But the `Some` member is on nongeneric `Option`, not on `Option<T>`.

There is a clear relationship between `Option<T>` and `Option`, `KeyValuePair<TKey, TValue>` and `KeyValuePair`, `Task<T>` and `Task`, `ImmutableArray<T>` and `ImmutableArray`, `Tensor<T>` and `Tensor`. The relationship is clear both from the naming convention and due to the static members on the factory type that return instances of the generic type.

It would be a lost opportunity not to make use of these clear relationships in order to make consumption more consistent.

#### Detailed design (factory containers)

As a guiding principle, the outcome for the call site can be thought of as equivalent to static extension methods being provided on the generic type which directly call the factory member on the generic type. The specifics below are aimed at this equivalence.

Member lookup for a _target-typed member binding expression_ would consider not just static members on the targeted type, but also _applicable factory members_, a subset of the static members of the target type's related _factory container types_.

A type `F` serves as a _factory container type_ for another type `G` if `F` has no type parameters of its own and `G` does have type parameters of its own, and they are both declared in the same module, and they are both declared in either the same containing type if any, or the same namespace if not.

A member of a _factory container type_ is an _applicable factory member_ if it is static and the result of evaluating the member has an identity conversion to the target type. This filter is consistent with how static extension methods work when the receiver type only makes it applicable to a narrower set of type parameters than is supported by the receiver type's declaration. This also becomes important for the potential future expansion of [explicit factory container associations](#future-expansions-factory-containers).

If the _target-typed member binding expression_ is the expression of an invocation expression, then the result of evaluating the invocation is considered instead, and any inferred type arguments in the invocation that appear in the return type will be inferred outside-in to match the target type. For example, `Option<short> opt = .Some(42)` will infer `Option.Some<short>`.

#### Alternatives (factory containers)

Static extension methods would be able to achieve the same end goal of writing `.Some(42)` for `Option<T>` or `.Range(1, 10)` for `IEnumerable<int>` or `.OrdinalCompareCase` for `IEqualityComparer<string>`. This would not fall foul of the [CA1000: Do not declare static members on generic types](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1000) rule, since the extension method itself is not declared on the generic type.

While this shows the power of combining the core proposal with static extension methods, there are scaling problems with using static extension methods to provide the missing consistency in consumption syntax.

1. This would not be automatic out of the box. It would become best practice to declare static extension methods on your own types to enable the nicer consumption pattern. This would not be done consistently, and users would run into the inconsistencies.
1. The core libraries would likely not be willing to declare such static extension methods for the core types. In general, they prefer not to bloat metadata by declaring simple forwarders to other methods.

#### Future expansions (factory containers)

We're keeping an option in our back pocket ([LDM notes](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-11.md#conclusion)) for factory containers to have an opt-in model to enable the same lookup in classes where the name does not match. For example:

- `IEnumerable<int> numbers = .Range(1, 10);` - Calls `Range` on the `Enumerable` type
- `IEqualityComparer<string> comparer = .OrdinalIgnoreCase;` - Calls `OrdinalIgnoreCase` on the `StringComparer` type
- `IEqualityComparer<T> comparer = .Default;` - Calls `Default` on `EqualityComparer<T>`

For the above examples to work, the `IEnumerable<T>` interface definition would be decorated with an attribute referring to the `Enumerable` class, and the `IEqualityComparer<T>` interface definition would be decorated with an attribute referring to the `StringComparer` and `EqualityComparer<>` classes.

This doesn't always involve generics, either. There is a community request for `Color color = .Red;` to refer to `Colors.Red`. This requires forming an association between the [`Color` struct](https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.color) and the [`Colors` class](https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.colors).

If the runtime placed on `IEnumerable<T>` a FactoryContainerAttribute pointing to `System.Linq.Enumerable` class, and a separate proposal were to make `IEnumerable<T>` the target type of spreads and foreach, the syntax `foreach (var x in .Range(1, 10))` or `[.. .Repeat(1, 10)]` would just fall out.

This is the definition of the attribute that enables a type to use factory members in another type when constructed through target-typed static member access:

```cs
namespace System.Runtime.CompilerServices;

[AttributeUsage(.Class | .Struct | .Interface | .Enum | .Delegate, AllowMultiple = true, Inherited = false)]
public sealed class FactoryContainerAttribute(Type containerType) : Attribute
{
    public Type ContainerType { get; } = containerType;
}
```

Errors will be produced in the following scenarios:

- If the attribute is used in older language versions.
- If the referenced type contains no members that could be _applicable factory members_ for any generic instantiation of the target type which are at least as accessible as the target type.

The filter of _applicable factory members_ in the [detailed design section](#detailed-design-factory-containers) means that `IEqualityComparer<string> comparer = .OrdinalIgnoreCase;` will work if `IEqualityComparer<T>` declares `[FactoryContainer(typeof(StringComparer))]`, but `IEqualityComparer<int> comparer = .OrdinalIgnoreCase;` will fail with an error that `.OrdinalIgnoreCase` cannot be found, rather than an error that it returns a comparer with an incompatible type. This is consistent with how a static extension member would behave.

## Specification

`'.' identifier type_argument_list?` is consolidated into a standalone syntax, `member_binding`, and this new syntax is added as a production of the [§12.8.7 Member access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1287-member-access) grammar:

```diff
 member_access
-    : primary_expression '.' identifier type_argument_list?
-    | predefined_type '.' identifier type_argument_list?
-    | qualified_alias_member '.' identifier type_argument_list?
+    : primary_expression member_binding
+    | predefined_type member_binding
+    | qualified_alias_member member_binding
+    | member_binding
     ;

+member_binding
+    '.' identifier type_argument_list?

 base_access
-    : 'base' '.' identifier type_argument_list?
+    : 'base' member_binding
     | 'base' '[' argument_list ']'
     ;

 null_conditional_member_access
-    : primary_expression '?' '.' identifier type_argument_list?
+    : primary_expression '?' member_binding
       (null_forgiving_operator? dependent_access)*
     ;

 dependent_access
-    : '.' identifier type_argument_list?    // member access
+    : member_binding            // member access
     | '[' argument_list ']'     // element access
     | '(' argument_list? ')'    // invocation
     ;

 null_conditional_projection_initializer
-    : primary_expression '?' '.' identifier type_argument_list?
+    : primary_expression '?' member_binding
     ;

 object_creation_expression
-    : 'new' type '(' argument_list? ')' object_or_collection_initializer?
-    | 'new' type object_or_collection_initializer
+    : 'new' (type | member_binding) '(' argument_list? ')' object_or_collection_initializer?
+    | 'new' (type | member_binding) object_or_collection_initializer
     ;
```

TODO: clarify that `member_access` is only valid as a `member_binding` if a conversion exists which binds it. No conversion exists which binds `.A.B`, `.A?.B`, `.A->B`, `.A().B`, and so on, so they are not valid expressions. Similar statement needed about `object_creation_expression` with `member_binding` for `new .A().B`.

TODO: disambiguation rule for `A ? . B ? . C : D`

TODO: patterns

## Limitations

One of the use cases this feature serves is production and consumption of values of nested derived types. But one consumption scenario that is left out of this improvement is `results.OfType<.Error>()`. It's not possible to target-type in this location because the `T` is not correlated with `results`. This problem would likely only be solvable in a general way with annotations that would need to ship with the `OfType` declaration.

A new operator could solve this, such as `results.SelectNonNull(r => r as .Error?)`.

## Drawbacks

### Ambiguities

There are a couple of ambiguities, with [parenthesized expressions](#ambiguity-with-parenthesized-expression) and [conditional expressions](#ambiguity-with-conditional-expression). See each link for details.

## Anti-drawbacks

There's been a separate request to mimic VB's `With` construct, allowing dotted access to the expression anywhere within the block:

```cs
with (expr)
{
    .PropOnExpr = 42;
    list.Add(.PropOnExpr);
}
```

This doesn't seem to be a popular request among the language team members who have commented on it. If we go ahead with the proposed target-typing for `.Name` syntax, this seals the fate of the requested `with` statement syntax shown here.

## Alternatives

### Workaround: `using static`

As a mitigation, `using static` directives can be applied as needed at the top of the file or globally. This allows syntax such as `GetMethod("Name", Public | Static)` today.

This comes with a severe limitation, however, in that it doesn't help much with generic types. If you import `Option<int>`, you can write `is Some`, but only for `Option<int>` and not `Option<string>` or any other constructed type.

Secondly, the `using static` workaround suffers from lack of precedence. Anything in scope with the same name takes precedence over the member you're trying to access. For example, `case IBinaryOperation { OperatorKind: Equals }` binds to `object.Equals` and fails. The proposed syntax for this feature solves this with the leading `.`, which unambiguously shows that the identifier that follows comes from the target type, and not from the current scope.

Third, the `using static` workaround is an imprecise hammer. It puts the names in scope in places where you might not want them. Imagine `var materialType = Slate;`: Maybe you thought this was an enum value in your roofing domain, but accidentally picked up a web color instead.

The `using static` approach has also not found broad adoption over fully qualifying. There are very low hit counts on grep.app and github.com for `using static System.Reflection.BindingFlags`.

### Alternative: no sigil

The target-typed static member access feature benefits from the precision of the `.` sigil, but it does not require a sigil. Here's how the feature would look without a sigil:

```cs
type.GetMethod("Name", Public | Instance | DeclaredOnly); // BindingFlags.Public | ...

control.ForeColor = Red;          // Color.Red
entity.InvoiceDate = Today;       // DateTime.Today

// Production (static members on Option<int>)
Option<int> option = condition ? None : Some(42);

// Production (nested derived types)
CustomResult result = condition ? new Success(42) : new Error("message");

// Consumption (nested derived types)
return result switch
{
    Success(var val) => val,
    Error => defaultVal,
};
```

A sigil is strongly recommended for two reasons: user comprehension, and power.

Firstly, the feature would be **harder to understand** without a sigil. The presence of `.` makes reading much more efficient. If no such marker is in place, it will slow down understanding of code, in both directions. Any identifier could suddenly be target-typing, and any existing occurrence of target-typing could change meaning with spooky action at a distance if conflicted names are imported, or defined in scope at some higher level.

For example, is `Instance` using target-typing? What about `Error`?

```cs
public ResultCodes PerformTask()
{
    var methods = type.GetMethods(Instance);
    // ...
    return Error;
}
```

Impossible to say without thinking through a lot of context. You'd have to know if there are any members named `Instance` or `Error` in the current class or base classes, or any containing classes and their base classes, or if there are any types in the namespace or in any imported namespaces named `Instance` or `Error`, or if any class is imported that has a static member named `Instance` or `Error`.

How about this? Is `ModifierKeys` ending up target-typed? Is `MaxValue`?

```cs
if (ModifierKeys == Keys.Control)
{
    Value = MaxValue;
}
```

A sigil thus provides essential context. With a sigil, there is no longer any spooky action at a distance which changes the fundamental meaning of the expression.

(The language has prior art in `default` and `new()`. In both cases, when the type is dropped, the remaining syntax cannot stand alone without a target type.)

Secondly, the feature would become **less powerful** without a sigil. To avoid changes in meaning, this would have to prefer binding to other things in the current scope name, with target-typing as a fallback. This would result in unpleasant interruptions with no recourse other than typing out the full type name. These interruptions are expected to be frequent enough to hamper the success of the feature.

This specific sigil is a good fit with modern language sensibilities and audiences. The Swift, Dart, and Zig languages have all added the `.xyz` syntax with the same meaning as this proposal for C#, and Rust has an RFC proposing the same dotted syntax:

- Swift calls it [implicit member expressions](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/expressions#Implicit-Member-Expression).
- Dart calls it "dot shorthands" or [static access shorthand](https://github.com/dart-lang/language/blob/c31243942eacadcc1be8cf81016f758fe831b99c/working/3616%20-%20enum%20value%20shorthand/proposal-simple-lrhn.md).
- Rust has an active RFC which calls it [path inference](https://github.com/rust-lang/rfcs/pull/3444).
- Zig calls it [enum literals](https://ziglang.org/documentation/master/#Enum-Literals).

## Answered questions

### Ambiguity with parenthesized expression

This is valid grammar today, which fails in binding if `A` is a type and not a value: `(A).B`.

The new grammar we're adding would allow this to be parsed as a cast followed by a target-typed static member access. This new interpretation is consistent with `(A)new()` and `(A)default` working today, but it would not be practically useful. `A.B` is a simpler and clearer way to write the same thing.

Should `(A).B` continue to fail when `A` is a type, or be made to work the same as `A.B`?

**Recommendation:** `(A).B` should continue to fail when `A` is a type. Even though blocking this syntax is an additional rule, the syntax is not beneficial.

**Conclusion**: Recommendation taken ([LDM notes](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-11.md#conclusion-1)).

### Ambiguity with conditional expression

There is an ambiguity if target-typed static member access is used as the first branch of a conditional expression, where it would parse today as a null-safe dereference: `expr ? .Name : ...`

We can follow the approach already taken for the similar ambiguity in collection expressions with `expr ? [` possibly being an indexer and possibly being a collection expression.

Alternatively, target-typed static member access could be always disallowed within the first branch of a conditional expression unless surrounded by parens: `expr ? (.Name) : ...`. The downside is that this puts a usability burden onto users, since the compiler can work out the ambiguity by looking ahead for the `:` as with collection expressions.

**Recommendation:** Allow `expr ? .Name :` by looking ahead for `:`, just as with collection expressions.

**Conclusion**: Recommendation taken ([LDM notes](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-11.md#conclusion-2)).

## Open questions

### Allowing overload resolution to be informed by target-typed access

In [Notes](#notes), it is currently specified that overload resolution is not influenced by `.Xyz` expressions. This is consistent with `new()`. One risk of allowing overload resolution to be influenced is that the following code would break if a new static member `CustomType.MinValue` was created:

```cs
M(.MinValue);

void M(int p) { }
void M(CustomType p) { }
```

This comes with one fairly significant downside in a common use case, which is that the following code fails:
```cs
// error CS0121: The call is ambiguous between the following methods or properties:
// 'Dictionary<TKey, TValue>.Dictionary(int)' and 'Dictionary<TKey, TValue>.Dictionary(IEqualityComparer<TKey>)'
Dictionary<string, int> d1 = new Dictionary<string, int>(.OrdinalIgnoreCase);

// Same issue here
Dictionary<string, int> d2 = [with(.OrdinalIgnoreCase), a: b, c: d];
```

Also consider more deeply nested expressions which allow target typed static member access, such as:
```cs
new Dictionary<string, int>(ignoreCase ? .OrdinalIgnoreCase : .Ordinal);
```

Is it worth making overload resolution smarter to allow this, perhaps by adding a tiebreaker when target-typed member access expressions resolve for exactly one overload?

Alternatively, because this issue (while significant) does not crop up in many APIs, is this a good use case for OverloadResolutionPriorityAttribute on constructors and Create methods that fall into this issue?

**Recommendation:** If the runtime is willing to use ORPA, keep target-typed member access free of interactions with overload resolution, just like target-typed new.

## Examples

### Pattern matching enums

```cs
public static string GetTypeString(ContractTransactionMode mode)
{
    return mode switch
    {
        .Preproduction => Preproduction,
        .MoltPreproduction => MoltPreproduction,
        .Production => ProductionFixedWeekly,
        .EggProduction => ProductionPerGradeADozen,
        .LayerBonus => LayerBonus,
        .PulletGrower or .PulletGrowerFinal or .PulletGrowingFinal
        or .PulletGrowingAP or .PulletGrowingAR or .PulletGrowerBonus => PulletGrowerOrGrowingPayment,
    };
}
```

```cs
if (alias.ItemAliasType is not (.Vendor or .User)) ...
```

### Returning enums

```cs
private static ContractTransactionMode GetMode(string paymentType)
{
    return paymentType switch
    {
        Preproduction => .Preproduction,
        MoltPreproduction => .MoltPreproduction,
        ProductionFixedWeekly => .Production,
        ProductionPerGradeADozen => .EggProduction,
        PulletGrowerOrGrowingPayment => .Pullet,
        LayerBonus => .LayerBonus,
    };
}
```

### Translating one enum to another

```cs
LineItemType lineItemType = transactionType switch
{
    .Invoice => .Sale,
    .CreditMemo => .Return,
};
```

### Using System.Reflection enums

All the focus is on the meaning and not on repeating the containing enum name four and five times:

```cs
foreach (var property in type.GetProperties(.Public | .NonPublic | .Static | .Instance | .DeclaredOnly))
{
    if (property.SetMethod is not { } setter)
    {
        continue;
    }

    if ((setter.Attributes & .MemberAccessMask) is not (.Public or .Family or .FamORAssem))
    {
        continue;
    }
    
    // ...
}
```

### Using generated interop (e.g. CsWin32)

It's typical to see generated enum member names be the same as the original constant, which is to say that the enum name itself is pretty redundant:

`FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE`

Instead, this could just mention the well-known constant names which are what you'd search anyway :

`.FILE_GENERIC_READ | .FILE_GENERIC_WRITE`

### Target-typing existing operand expressions

[Target-typing with overloadable operators](#target-typing-with-overloadable-operators) currently allows other target-typed expressions besides `.Xyz` to benefit from the new target-typing, such as `null`, `default` and `[]`. One exception to this is the target-typed `new` expression. The spec explicitly states that target-typed `new` may not appear as an operand of a unary or binary operator. If desired, we could lift this restriction so that it is not the odd one out:

```cs
Point p = origin + new(50, 100);
```

Do we want to lift this restriction and enable `+ new()` alongside `+ default` and `+ []` and `+ .Instance` and so on? Do we want to keep this restriction in place so that `+ new()` remains the odd one out? Or, do we want to narrow this ability so that `+ .Instance` is the only target-typed operand expression form that is allowed and `+ default` and `+ []` are not allowed?

(Note: `+` is being used for demonstration purposes, but this applies to every overloadable unary and binary operator.)

Recommendation: This would be a nice-to-have, but is not critical to the target-typed static member access proposal. Choose the simpler implementation path for now and expand later if feedback supports it.
