From 90ac0d947d5395cbcd17c56183826560a6a79324 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:26:10 +0000 Subject: [PATCH 01/10] Initial plan From 7d22b0a9ba0facfe823d9b96da22828a185a3aa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:29:00 +0000 Subject: [PATCH 02/10] Add proposal for non-boxing default-interface methods Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- .../non-boxing-default-interface-methods.md | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 proposals/non-boxing-default-interface-methods.md diff --git a/proposals/non-boxing-default-interface-methods.md b/proposals/non-boxing-default-interface-methods.md new file mode 100644 index 0000000000..781f902233 --- /dev/null +++ b/proposals/non-boxing-default-interface-methods.md @@ -0,0 +1,256 @@ +# Non-boxing default-interface methods + +Champion issue: + +## Summary + +Introduce a syntax for declaring default interface methods that avoid boxing when implemented by value types. This is achieved by using a `static this` modifier that transforms the method into a static virtual method with an explicit receiver parameter, enabling non-boxing invocation on structs. + +## Motivation + +When default interface methods were added to C#, the type of `this` in such methods is the interface type itself. While this is not problematic for classes, for structs it implies boxing. This has implications not just for performance, but also for semantics, as modifications to the struct within the method affect the boxed copy rather than the original value. + +Consider the following interface with a default method: + +```csharp +interface ICounter +{ + int Count { get; set; } + + void Increment() => Count++; +} +``` + +When a struct implements this interface and the default method is called, the struct is boxed: + +```csharp +struct Counter : ICounter +{ + public int Count { get; set; } + // Uses default Increment() implementation +} + +var c = new Counter(); +((ICounter)c).Increment(); // Boxes 'c', increments the boxed copy +Console.WriteLine(c.Count); // Still 0, not 1 +``` + +This behavior is both unexpected and can cause subtle bugs. It would be beneficial to have a way to define default interface methods that work with the actual struct type rather than a boxed interface reference. + +## Detailed design + +### Syntax + +A new modifier combination `static this` is introduced for interface members. When applied to a method, property, or indexer, it indicates that the member should be treated as a static virtual member with an explicit receiver parameter: + +```csharp +interface IFace where TSelf : IFace +{ + static this int M(ref TSelf @this) => 0; +} +``` + +The `static this` modifier: +- Takes the place of `virtual`/`abstract` modifiers +- Indicates the member has an explicit receiver parameter +- Whether the member is abstract or has a default implementation is inferred based on whether a body is provided + +### Grammar changes + +The grammar for interface members is extended to allow the `static this` modifier combination: + +```diff + interface_method_declaration +- : attributes? 'new'? ('abstract' | 'virtual' | 'sealed')? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* ';' ++ : attributes? 'new'? ('abstract' | 'virtual' | 'sealed' | 'static' 'this')? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* (';' | method_body) + ; +``` + +### Semantic rules + +When `static this` is applied to an interface member: + +1. The member becomes a static virtual (or static abstract if no body) member +2. The first parameter must be a reference to the self-constrained type parameter (using `ref`, `in`, or `ref readonly`) +3. The self-constrained type parameter must be constrained to the containing interface +4. Implementations can override this member using explicit interface implementation with the concrete type + +### Lowering + +The `static this` syntax is lowered to the existing runtime encoding for static virtual methods combined with extension methods for convenient invocation. + +#### Source code + +```csharp +interface IFace where TSelf : IFace +{ + static this int M(ref TSelf @this) => 0; +} + +struct S(int x) : IFace +{ + private readonly int _x = x; + static int IFace.M(ref S @this) => @this._x; +} +``` + +#### Lowered equivalent + +The compiler transforms the above into the following. Note that the extension syntax uses the [C# 14 extension members](csharp-14.0/extensions.md) feature: + +```csharp +interface IFace where TSelf : IFace +{ + static virtual int M(ref TSelf @this) => 0; +} + +struct S(int x) : IFace +{ + private readonly int _x = x; + static int IFace.M(ref S @this) => @this._x; +} + +// Compiler-generated extension for convenient invocation +static class IFaceExtensions +{ + extension(ref T @this) where T : struct, IFace + { + public int M() => T.M(ref @this); + } +} +``` + +### Usage + +With this feature, users can write: + +```csharp +var s = new S(1); +Console.WriteLine(s.M()); // Outputs: 1 +``` + +The call to `s.M()` is resolved to the extension method, which in turn calls the static virtual method. No boxing occurs because the struct is passed by reference. + +### Properties and indexers + +The `static this` modifier can also be applied to properties and indexers: + +```csharp +interface IHasValue where TSelf : IHasValue +{ + static this int Value { get; } +} + +// Lowered equivalent: +interface IHasValue where TSelf : IHasValue +{ + static virtual int get_Value(ref TSelf @this); +} +``` + +### Implementation in structs + +When a struct implements an interface with `static this` members, it can provide an implementation using explicit interface implementation: + +```csharp +struct MyStruct : IFace +{ + private int _value; + + // Explicit implementation of the static this member + static int IFace.M(ref MyStruct @this) => @this._value; +} +``` + +### Default implementations + +If a `static this` member has a body, that body serves as the default implementation: + +```csharp +interface ICloneable where TSelf : ICloneable +{ + // Abstract - no default implementation + static this TSelf Clone(ref TSelf @this); + + // Has default implementation + static this TSelf CloneAndModify(ref TSelf @this, Action modify) + { + var clone = TSelf.Clone(ref @this); + modify(clone); + return clone; + } +} +``` + +## Drawbacks + +### Complexity + +- Introduces a new modifier combination (`static this`) that may be confusing to developers unfamiliar with the feature +- The lowering involves generating extension methods, which adds to compilation complexity +- Developers must understand the difference between regular default interface methods and `static this` members + +### Breaking changes + +- None anticipated, as this is purely additive syntax + +### Interop concerns + +- The lowered form uses existing CLR features (static virtual methods), so runtime support should not be an issue +- However, languages that don't understand the extension generation pattern may not be able to use these members conveniently + +## Alternatives + +### Manual static virtual + extension pattern + +Developers can already achieve this behavior manually using static virtual methods and [C# 14 extension members](csharp-14.0/extensions.md): + +```csharp +interface IFace where TSelf : IFace +{ + static virtual int M(ref TSelf @this) => 0; +} + +static class IFaceExt +{ + extension(ref T @this) where T : struct, IFace + { + public int M() => T.M(ref @this); + } +} +``` + +However, this requires significant boilerplate and is error-prone. The proposed syntax automates this pattern. + +### Using `virtual` with explicit receiver + +An alternative syntax could repurpose existing modifiers: + +```csharp +interface IFace where TSelf : IFace +{ + virtual int M(ref TSelf @this) => 0; +} +``` + +This was rejected because it doesn't clearly indicate that the method is static and would conflict with existing virtual method semantics. + +### Do nothing + +Developers could continue using the manual pattern or accept boxing for default interface methods on structs. However, the boilerplate required is substantial and the boxing behavior causes subtle bugs. + +## Open questions + +1. **Extension method generation**: Should the compiler always generate extension methods, or should this be opt-in? + +2. **Class support**: Should `static this` members also work with classes, or only with struct constraints? + +3. **Ref kind flexibility**: Should the receiver parameter support `ref`, `in`, `ref readonly`, or all of them? The proposal currently allows any ref kind. + +4. **Naming**: Is `static this` the best modifier combination, or would alternatives like `instance static` or a new keyword be clearer? + +5. **Visibility of generated extensions**: Should the generated extension methods be public or should there be a way to control their visibility? + +## Design meetings + +TBD From eb17ad313af0d69285d30d157d9793babd88dfb9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:33:09 +0000 Subject: [PATCH 03/10] Update proposal: treat static this as instance method, not generate extensions Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- .../non-boxing-default-interface-methods.md | 64 +++++++++++++------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/proposals/non-boxing-default-interface-methods.md b/proposals/non-boxing-default-interface-methods.md index 781f902233..775e79ba9a 100644 --- a/proposals/non-boxing-default-interface-methods.md +++ b/proposals/non-boxing-default-interface-methods.md @@ -77,7 +77,11 @@ When `static this` is applied to an interface member: ### Lowering -The `static this` syntax is lowered to the existing runtime encoding for static virtual methods combined with extension methods for convenient invocation. +The `static this` syntax is lowered to a static virtual method in the interface. However, rather than generating actual extension methods, the `static this` member is **treated as an instance method on the containing interface**. This means: + +- The method can be invoked using instance method syntax on values of the implementing type +- For structs, the receiver is passed by reference (avoiding boxing) +- Method resolution treats these members as instance methods, even though the underlying implementation uses static virtual methods #### Source code @@ -96,7 +100,7 @@ struct S(int x) : IFace #### Lowered equivalent -The compiler transforms the above into the following. Note that the extension syntax uses the [C# 14 extension members](csharp-14.0/extensions.md) feature: +The compiler transforms the above into: ```csharp interface IFace where TSelf : IFace @@ -109,17 +113,10 @@ struct S(int x) : IFace private readonly int _x = x; static int IFace.M(ref S @this) => @this._x; } - -// Compiler-generated extension for convenient invocation -static class IFaceExtensions -{ - extension(ref T @this) where T : struct, IFace - { - public int M() => T.M(ref @this); - } -} ``` +The key insight is that no extension methods are generated. Instead, the compiler treats the `static this` member as an instance method for lookup and invocation purposes, while the underlying implementation uses static virtual methods. + ### Usage With this feature, users can write: @@ -129,7 +126,33 @@ var s = new S(1); Console.WriteLine(s.M()); // Outputs: 1 ``` -The call to `s.M()` is resolved to the extension method, which in turn calls the static virtual method. No boxing occurs because the struct is passed by reference. +The call to `s.M()` is resolved by the compiler as if `M` were an instance method on the interface. The compiler generates a call to the static virtual method, passing the receiver by reference. No boxing occurs because the struct is never converted to the interface type. + +### Signature collision rules + +Because `static this` members are treated as instance methods on the interface, signature collision rules apply. For the purposes of determining signature collisions, the receiver parameter of a `static this` member is excluded (since it represents the implicit `this` when invoked as an instance method). + +Therefore, a `static this void M(ref TSelf @this)` has the "invocation signature" of `void M()`, and it is an error to declare both: + +```csharp +interface IExample where TSelf : IExample +{ + void Foo(); // Instance method with signature Foo() + static this void Foo(ref TSelf @this); // Error: invocation signature is also Foo() +} +``` + +Additional parameters beyond the receiver are included in the signature: + +```csharp +interface IExample where TSelf : IExample +{ + void Bar(); // Instance method Bar() + static this void Bar(ref TSelf @this, int value); // OK: invocation signature is Bar(int) +} +``` + +This rule ensures that method resolution is unambiguous when invoking members on implementing types. ### Properties and indexers @@ -187,8 +210,9 @@ interface ICloneable where TSelf : ICloneable ### Complexity - Introduces a new modifier combination (`static this`) that may be confusing to developers unfamiliar with the feature -- The lowering involves generating extension methods, which adds to compilation complexity +- The compiler must treat these members as instance methods for resolution purposes while emitting static virtual methods, adding implementation complexity - Developers must understand the difference between regular default interface methods and `static this` members +- Signature collision rules add additional constraints to learn ### Breaking changes @@ -197,7 +221,7 @@ interface ICloneable where TSelf : ICloneable ### Interop concerns - The lowered form uses existing CLR features (static virtual methods), so runtime support should not be an issue -- However, languages that don't understand the extension generation pattern may not be able to use these members conveniently +- Languages that don't understand the `static this` semantic treatment may see these as regular static virtual methods and require explicit static invocation syntax ## Alternatives @@ -220,7 +244,7 @@ static class IFaceExt } ``` -However, this requires significant boilerplate and is error-prone. The proposed syntax automates this pattern. +However, this requires significant boilerplate and is error-prone. The proposed `static this` syntax achieves the same semantics without requiring explicit extension method definitions. ### Using `virtual` with explicit receiver @@ -241,15 +265,15 @@ Developers could continue using the manual pattern or accept boxing for default ## Open questions -1. **Extension method generation**: Should the compiler always generate extension methods, or should this be opt-in? +1. **Class support**: Should `static this` members also work with classes, or only with struct constraints? -2. **Class support**: Should `static this` members also work with classes, or only with struct constraints? +2. **Ref kind flexibility**: Should the receiver parameter support `ref`, `in`, `ref readonly`, or all of them? The proposal currently allows any ref kind. -3. **Ref kind flexibility**: Should the receiver parameter support `ref`, `in`, `ref readonly`, or all of them? The proposal currently allows any ref kind. +3. **Naming**: Is `static this` the best modifier combination, or would alternatives like `instance static` or a new keyword be clearer? -4. **Naming**: Is `static this` the best modifier combination, or would alternatives like `instance static` or a new keyword be clearer? +4. **Lookup precedence**: When a `static this` member and a true instance member could both match, what are the exact precedence rules? -5. **Visibility of generated extensions**: Should the generated extension methods be public or should there be a way to control their visibility? +5. **Explicit invocation syntax**: Should there be a way to explicitly invoke the underlying static virtual method (e.g., for cases where the implicit instance-like syntax is not desired)? ## Design meetings From 402dd7949924c170e0e1bad331fa87614428b73f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:35:04 +0000 Subject: [PATCH 04/10] Use ICounter definition in Syntax section for consistency Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- proposals/non-boxing-default-interface-methods.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/proposals/non-boxing-default-interface-methods.md b/proposals/non-boxing-default-interface-methods.md index 775e79ba9a..0692f7abb2 100644 --- a/proposals/non-boxing-default-interface-methods.md +++ b/proposals/non-boxing-default-interface-methods.md @@ -44,9 +44,11 @@ This behavior is both unexpected and can cause subtle bugs. It would be benefici A new modifier combination `static this` is introduced for interface members. When applied to a method, property, or indexer, it indicates that the member should be treated as a static virtual member with an explicit receiver parameter: ```csharp -interface IFace where TSelf : IFace +interface ICounter where TSelf : ICounter { - static this int M(ref TSelf @this) => 0; + int Count { get; set; } + + static this void Increment(ref TSelf @this) => @this.Count++; } ``` From ea545178a0b5e0a37f0a7759db9ce1895be9925a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:43:41 +0000 Subject: [PATCH 05/10] Replace all IFace examples with ICounter throughout the file Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- .../non-boxing-default-interface-methods.md | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/proposals/non-boxing-default-interface-methods.md b/proposals/non-boxing-default-interface-methods.md index 0692f7abb2..1221543d47 100644 --- a/proposals/non-boxing-default-interface-methods.md +++ b/proposals/non-boxing-default-interface-methods.md @@ -88,15 +88,17 @@ The `static this` syntax is lowered to a static virtual method in the interface. #### Source code ```csharp -interface IFace where TSelf : IFace +interface ICounter where TSelf : ICounter { - static this int M(ref TSelf @this) => 0; + int Count { get; set; } + + static this void Increment(ref TSelf @this) => @this.Count++; } -struct S(int x) : IFace +struct Counter : ICounter { - private readonly int _x = x; - static int IFace.M(ref S @this) => @this._x; + public int Count { get; set; } + // Uses default Increment() implementation } ``` @@ -105,15 +107,17 @@ struct S(int x) : IFace The compiler transforms the above into: ```csharp -interface IFace where TSelf : IFace +interface ICounter where TSelf : ICounter { - static virtual int M(ref TSelf @this) => 0; + int Count { get; set; } + + static virtual void Increment(ref TSelf @this) => @this.Count++; } -struct S(int x) : IFace +struct Counter : ICounter { - private readonly int _x = x; - static int IFace.M(ref S @this) => @this._x; + public int Count { get; set; } + // Uses default Increment() implementation } ``` @@ -124,11 +128,12 @@ The key insight is that no extension methods are generated. Instead, the compile With this feature, users can write: ```csharp -var s = new S(1); -Console.WriteLine(s.M()); // Outputs: 1 +var c = new Counter(); +c.Increment(); +Console.WriteLine(c.Count); // Outputs: 1 ``` -The call to `s.M()` is resolved by the compiler as if `M` were an instance method on the interface. The compiler generates a call to the static virtual method, passing the receiver by reference. No boxing occurs because the struct is never converted to the interface type. +The call to `c.Increment()` is resolved by the compiler as if `Increment` were an instance method on the interface. The compiler generates a call to the static virtual method, passing the receiver by reference. No boxing occurs because the struct is never converted to the interface type. ### Signature collision rules @@ -178,12 +183,12 @@ interface IHasValue where TSelf : IHasValue When a struct implements an interface with `static this` members, it can provide an implementation using explicit interface implementation: ```csharp -struct MyStruct : IFace +struct Counter : ICounter { - private int _value; + public int Count { get; set; } // Explicit implementation of the static this member - static int IFace.M(ref MyStruct @this) => @this._value; + static void ICounter.Increment(ref Counter @this) => @this.Count += 2; // Custom increment } ``` @@ -232,16 +237,18 @@ interface ICloneable where TSelf : ICloneable Developers can already achieve this behavior manually using static virtual methods and [C# 14 extension members](csharp-14.0/extensions.md): ```csharp -interface IFace where TSelf : IFace +interface ICounter where TSelf : ICounter { - static virtual int M(ref TSelf @this) => 0; + int Count { get; set; } + + static virtual void Increment(ref TSelf @this) => @this.Count++; } -static class IFaceExt +static class ICounterExt { - extension(ref T @this) where T : struct, IFace + extension(ref T @this) where T : struct, ICounter { - public int M() => T.M(ref @this); + public void Increment() => T.Increment(ref @this); } } ``` @@ -253,9 +260,11 @@ However, this requires significant boilerplate and is error-prone. The proposed An alternative syntax could repurpose existing modifiers: ```csharp -interface IFace where TSelf : IFace +interface ICounter where TSelf : ICounter { - virtual int M(ref TSelf @this) => 0; + int Count { get; set; } + + virtual void Increment(ref TSelf @this) => @this.Count++; } ``` From b0ec6e84456ce0c3660902ee6e78412356fd555c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 04:00:02 +0000 Subject: [PATCH 06/10] Simplify syntax: use just `this` modifier with implicit receiver Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- .../non-boxing-default-interface-methods.md | 115 +++++++++++------- 1 file changed, 70 insertions(+), 45 deletions(-) diff --git a/proposals/non-boxing-default-interface-methods.md b/proposals/non-boxing-default-interface-methods.md index 1221543d47..e5c364317e 100644 --- a/proposals/non-boxing-default-interface-methods.md +++ b/proposals/non-boxing-default-interface-methods.md @@ -4,7 +4,7 @@ Champion issue: ## Summary -Introduce a syntax for declaring default interface methods that avoid boxing when implemented by value types. This is achieved by using a `static this` modifier that transforms the method into a static virtual method with an explicit receiver parameter, enabling non-boxing invocation on structs. +Introduce a syntax for declaring default interface methods that avoid boxing when implemented by value types. This is achieved by using a `this` modifier on interface members, which provides an implicit receiver typed as the self-constrained type parameter, enabling non-boxing invocation on structs. ## Motivation @@ -41,45 +41,72 @@ This behavior is both unexpected and can cause subtle bugs. It would be benefici ### Syntax -A new modifier combination `static this` is introduced for interface members. When applied to a method, property, or indexer, it indicates that the member should be treated as a static virtual member with an explicit receiver parameter: +A new `this` modifier is introduced for interface members. When applied to a method, property, or indexer, it indicates that the member has an implicit receiver typed as the self-constrained type parameter (rather than the interface type): ```csharp interface ICounter where TSelf : ICounter { int Count { get; set; } - static this void Increment(ref TSelf @this) => @this.Count++; + this void Increment() => Count++; } ``` -The `static this` modifier: +The `this` modifier: +- Indicates the member has an implicit receiver of type `TSelf` (the first type parameter with a recursive constraint) - Takes the place of `virtual`/`abstract` modifiers -- Indicates the member has an explicit receiver parameter - Whether the member is abstract or has a default implementation is inferred based on whether a body is provided +### Type parameter requirements + +The `this` modifier requires the interface to have a self-constrained type parameter: +- The type parameter must be the **first** type parameter on the interface +- It must have a **recursive constraint** to the containing interface (e.g., `TSelf : ICounter`) + +```csharp +// Valid: TSelf is first parameter with recursive constraint +interface IExample where TSelf : IExample +{ + this void M(); +} + +// Invalid: no self-constrained type parameter +interface IInvalid +{ + this void M(); // Error: interface must have a self-constrained type parameter +} + +// Invalid: self-constraint is not on the first type parameter +interface IInvalidParameterOrder where TSelf : IInvalidParameterOrder +{ + this void M(); // Error: TSelf must be the first type parameter +} +``` + ### Grammar changes -The grammar for interface members is extended to allow the `static this` modifier combination: +The grammar for interface members is extended to allow the `this` modifier: ```diff interface_method_declaration - : attributes? 'new'? ('abstract' | 'virtual' | 'sealed')? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* ';' -+ : attributes? 'new'? ('abstract' | 'virtual' | 'sealed' | 'static' 'this')? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* (';' | method_body) ++ : attributes? 'new'? ('abstract' | 'virtual' | 'sealed' | 'this')? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* (';' | method_body) ; ``` ### Semantic rules -When `static this` is applied to an interface member: +When `this` is applied to an interface member: -1. The member becomes a static virtual (or static abstract if no body) member -2. The first parameter must be a reference to the self-constrained type parameter (using `ref`, `in`, or `ref readonly`) -3. The self-constrained type parameter must be constrained to the containing interface -4. Implementations can override this member using explicit interface implementation with the concrete type +1. The interface must have a first type parameter with a recursive constraint to the containing interface +2. Within the member body, `this` has the type of that self-constrained type parameter (not the interface type) +3. For structs, the receiver is passed by reference (avoiding boxing) +4. The member is lowered to a static virtual method with an explicit receiver parameter +5. Implementations can override this member using explicit interface implementation ### Lowering -The `static this` syntax is lowered to a static virtual method in the interface. However, rather than generating actual extension methods, the `static this` member is **treated as an instance method on the containing interface**. This means: +The `this` modifier is lowered to a static virtual method with an explicit receiver parameter. However, the member is **treated as an instance method on the containing interface** for lookup and invocation purposes. This means: - The method can be invoked using instance method syntax on values of the implementing type - For structs, the receiver is passed by reference (avoiding boxing) @@ -92,7 +119,7 @@ interface ICounter where TSelf : ICounter { int Count { get; set; } - static this void Increment(ref TSelf @this) => @this.Count++; + this void Increment() => Count++; } struct Counter : ICounter @@ -121,7 +148,7 @@ struct Counter : ICounter } ``` -The key insight is that no extension methods are generated. Instead, the compiler treats the `static this` member as an instance method for lookup and invocation purposes, while the underlying implementation uses static virtual methods. +The key insight is that the `this` modifier provides a simpler syntax that lowers to static virtual methods with explicit receiver parameters. The compiler generates the receiver parameter automatically. ### Usage @@ -137,25 +164,23 @@ The call to `c.Increment()` is resolved by the compiler as if `Increment` were a ### Signature collision rules -Because `static this` members are treated as instance methods on the interface, signature collision rules apply. For the purposes of determining signature collisions, the receiver parameter of a `static this` member is excluded (since it represents the implicit `this` when invoked as an instance method). - -Therefore, a `static this void M(ref TSelf @this)` has the "invocation signature" of `void M()`, and it is an error to declare both: +Because `this` members are treated as instance methods on the interface, signature collision rules apply. A `this` member with the same name and parameter types as an instance method is an error: ```csharp interface IExample where TSelf : IExample { - void Foo(); // Instance method with signature Foo() - static this void Foo(ref TSelf @this); // Error: invocation signature is also Foo() + void Foo(); // Instance method with signature Foo() + this void Foo(); // Error: collision with Foo() } ``` -Additional parameters beyond the receiver are included in the signature: +Members with different parameters are allowed: ```csharp interface IExample where TSelf : IExample { - void Bar(); // Instance method Bar() - static this void Bar(ref TSelf @this, int value); // OK: invocation signature is Bar(int) + void Bar(); // Instance method Bar() + this void Bar(int value); // OK: signature is Bar(int) } ``` @@ -163,12 +188,12 @@ This rule ensures that method resolution is unambiguous when invoking members on ### Properties and indexers -The `static this` modifier can also be applied to properties and indexers: +The `this` modifier can also be applied to properties and indexers: ```csharp interface IHasValue where TSelf : IHasValue { - static this int Value { get; } + this int Value { get; } } // Lowered equivalent: @@ -180,32 +205,32 @@ interface IHasValue where TSelf : IHasValue ### Implementation in structs -When a struct implements an interface with `static this` members, it can provide an implementation using explicit interface implementation: +When a struct implements an interface with `this` members, it can provide an implementation using explicit interface implementation: ```csharp struct Counter : ICounter { public int Count { get; set; } - // Explicit implementation of the static this member + // Explicit implementation of the this member static void ICounter.Increment(ref Counter @this) => @this.Count += 2; // Custom increment } ``` ### Default implementations -If a `static this` member has a body, that body serves as the default implementation: +If a `this` member has a body, that body serves as the default implementation: ```csharp interface ICloneable where TSelf : ICloneable { // Abstract - no default implementation - static this TSelf Clone(ref TSelf @this); + this TSelf Clone(); - // Has default implementation - static this TSelf CloneAndModify(ref TSelf @this, Action modify) + // Has default implementation - can call other 'this' members directly + this TSelf CloneAndModify(Action modify) { - var clone = TSelf.Clone(ref @this); + var clone = Clone(); // Calls this.Clone() implicitly modify(clone); return clone; } @@ -216,10 +241,10 @@ interface ICloneable where TSelf : ICloneable ### Complexity -- Introduces a new modifier combination (`static this`) that may be confusing to developers unfamiliar with the feature +- Introduces a new `this` modifier that may be confusing to developers unfamiliar with the feature - The compiler must treat these members as instance methods for resolution purposes while emitting static virtual methods, adding implementation complexity -- Developers must understand the difference between regular default interface methods and `static this` members -- Signature collision rules add additional constraints to learn +- Developers must understand the difference between regular default interface methods and `this` members +- Requires understanding the type parameter requirements (first parameter, recursive constraint) ### Breaking changes @@ -228,7 +253,7 @@ interface ICloneable where TSelf : ICloneable ### Interop concerns - The lowered form uses existing CLR features (static virtual methods), so runtime support should not be an issue -- Languages that don't understand the `static this` semantic treatment may see these as regular static virtual methods and require explicit static invocation syntax +- Languages that don't understand the `this` semantic treatment may see these as regular static virtual methods and require explicit static invocation syntax ## Alternatives @@ -253,22 +278,22 @@ static class ICounterExt } ``` -However, this requires significant boilerplate and is error-prone. The proposed `static this` syntax achieves the same semantics without requiring explicit extension method definitions. +However, this requires significant boilerplate and is error-prone. The proposed `this` modifier achieves the same semantics without requiring explicit extension method definitions. -### Using `virtual` with explicit receiver +### Using `static this` with explicit receiver -An alternative syntax could repurpose existing modifiers: +An alternative syntax could require an explicit receiver parameter: ```csharp interface ICounter where TSelf : ICounter { int Count { get; set; } - virtual void Increment(ref TSelf @this) => @this.Count++; + static this void Increment(ref TSelf @this) => @this.Count++; } ``` -This was rejected because it doesn't clearly indicate that the method is static and would conflict with existing virtual method semantics. +This was considered but rejected in favor of the simpler implicit receiver syntax, which is more consistent with how instance methods work. ### Do nothing @@ -276,13 +301,13 @@ Developers could continue using the manual pattern or accept boxing for default ## Open questions -1. **Class support**: Should `static this` members also work with classes, or only with struct constraints? +1. **Class support**: Should `this` members also work with classes, or only with struct constraints? -2. **Ref kind flexibility**: Should the receiver parameter support `ref`, `in`, `ref readonly`, or all of them? The proposal currently allows any ref kind. +2. **Ref kind**: Should the implicit receiver be `ref`, `in`, or `ref readonly`? The proposal currently assumes `ref` for mutability. -3. **Naming**: Is `static this` the best modifier combination, or would alternatives like `instance static` or a new keyword be clearer? +3. **Naming**: Is `this` the best modifier, or would alternatives like `self` or a new keyword be clearer? -4. **Lookup precedence**: When a `static this` member and a true instance member could both match, what are the exact precedence rules? +4. **Lookup precedence**: When a `this` member and a true instance member could both match, what are the exact precedence rules? 5. **Explicit invocation syntax**: Should there be a way to explicitly invoke the underlying static virtual method (e.g., for cases where the implicit instance-like syntax is not desired)? From b7ff24d549ca8855eab39fd109df40c005e59dab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 04:12:59 +0000 Subject: [PATCH 07/10] Clarify receiver semantics: all receivers passed by ref, class handling Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- .../non-boxing-default-interface-methods.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/proposals/non-boxing-default-interface-methods.md b/proposals/non-boxing-default-interface-methods.md index e5c364317e..6d29cbd047 100644 --- a/proposals/non-boxing-default-interface-methods.md +++ b/proposals/non-boxing-default-interface-methods.md @@ -100,7 +100,9 @@ When `this` is applied to an interface member: 1. The interface must have a first type parameter with a recursive constraint to the containing interface 2. Within the member body, `this` has the type of that self-constrained type parameter (not the interface type) -3. For structs, the receiver is passed by reference (avoiding boxing) +3. The receiver is always passed by reference + - For structs, this avoids boxing + - For classes, the caller passes a reference to a temporary local copy of the reference variable (to avoid mutating the caller's variable if the method reassigns `this`) 4. The member is lowered to a static virtual method with an explicit receiver parameter 5. Implementations can override this member using explicit interface implementation @@ -109,7 +111,9 @@ When `this` is applied to an interface member: The `this` modifier is lowered to a static virtual method with an explicit receiver parameter. However, the member is **treated as an instance method on the containing interface** for lookup and invocation purposes. This means: - The method can be invoked using instance method syntax on values of the implementing type -- For structs, the receiver is passed by reference (avoiding boxing) +- The receiver is always passed by reference, regardless of whether it's a struct or class + - For structs, this avoids boxing + - For classes, the caller passes a reference to a temporary local copy of the reference variable (to avoid mutating the caller's variable if the method reassigns `this`) - Method resolution treats these members as instance methods, even though the underlying implementation uses static virtual methods #### Source code @@ -301,15 +305,13 @@ Developers could continue using the manual pattern or accept boxing for default ## Open questions -1. **Class support**: Should `this` members also work with classes, or only with struct constraints? +1. **Ref kind**: Should the implicit receiver be `ref`, `in`, or `ref readonly`? The proposal currently assumes `ref` for mutability. -2. **Ref kind**: Should the implicit receiver be `ref`, `in`, or `ref readonly`? The proposal currently assumes `ref` for mutability. +2. **Naming**: Is `this` the best modifier, or would alternatives like `self` or a new keyword be clearer? -3. **Naming**: Is `this` the best modifier, or would alternatives like `self` or a new keyword be clearer? +3. **Lookup precedence**: When a `this` member and a true instance member could both match, what are the exact precedence rules? -4. **Lookup precedence**: When a `this` member and a true instance member could both match, what are the exact precedence rules? - -5. **Explicit invocation syntax**: Should there be a way to explicitly invoke the underlying static virtual method (e.g., for cases where the implicit instance-like syntax is not desired)? +4. **Explicit invocation syntax**: Should there be a way to explicitly invoke the underlying static virtual method (e.g., for cases where the implicit instance-like syntax is not desired)? ## Design meetings From 76ad9e2cb3168060bc7248b659267f455cd84654 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 18:42:21 +0000 Subject: [PATCH 08/10] Use method-level type parameter for receiver instead of interface-level Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- .../non-boxing-default-interface-methods.md | 129 +++++++++--------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/proposals/non-boxing-default-interface-methods.md b/proposals/non-boxing-default-interface-methods.md index 6d29cbd047..be46647402 100644 --- a/proposals/non-boxing-default-interface-methods.md +++ b/proposals/non-boxing-default-interface-methods.md @@ -4,7 +4,7 @@ Champion issue: ## Summary -Introduce a syntax for declaring default interface methods that avoid boxing when implemented by value types. This is achieved by using a `this` modifier on interface members, which provides an implicit receiver typed as the self-constrained type parameter, enabling non-boxing invocation on structs. +Introduce a syntax for declaring default interface methods that avoid boxing when implemented by value types. This is achieved by using a `this` modifier on interface members, which provides an implicit receiver typed as the method-level type parameter, enabling non-boxing invocation on structs. ## Motivation @@ -41,65 +41,65 @@ This behavior is both unexpected and can cause subtle bugs. It would be benefici ### Syntax -A new `this` modifier is introduced for interface members. When applied to a method, property, or indexer, it indicates that the member has an implicit receiver typed as the self-constrained type parameter (rather than the interface type): +A new `this` modifier is introduced for interface members. When applied to a method, property, or indexer, it declares a type parameter for the receiver and indicates that the member has an implicit receiver typed as that type parameter (rather than the interface type): ```csharp -interface ICounter where TSelf : ICounter +interface ICounter { int Count { get; set; } - this void Increment() => Count++; + this void Increment() where TSelf : ICounter => Count++; } ``` -The `this` modifier: -- Indicates the member has an implicit receiver of type `TSelf` (the first type parameter with a recursive constraint) +The `this` modifier: +- Declares a type parameter `TSelf` that represents the receiver type +- The type parameter must be constrained to the containing interface +- Provides an implicit receiver of type `TSelf` within the method body - Takes the place of `virtual`/`abstract` modifiers - Whether the member is abstract or has a default implementation is inferred based on whether a body is provided ### Type parameter requirements -The `this` modifier requires the interface to have a self-constrained type parameter: -- The type parameter must be the **first** type parameter on the interface -- It must have a **recursive constraint** to the containing interface (e.g., `TSelf : ICounter`) +The type parameter on the `this` modifier must be constrained to the containing interface: ```csharp -// Valid: TSelf is first parameter with recursive constraint -interface IExample where TSelf : IExample +// Valid: TSelf is constrained to IExample +interface IExample { - this void M(); + this void M() where TSelf : IExample; } -// Invalid: no self-constrained type parameter +// Invalid: no constraint to the containing interface interface IInvalid { - this void M(); // Error: interface must have a self-constrained type parameter + this void M(); // Error: TSelf must be constrained to IInvalid } -// Invalid: self-constraint is not on the first type parameter -interface IInvalidParameterOrder where TSelf : IInvalidParameterOrder +// Valid: additional constraints are allowed +interface IComparable { - this void M(); // Error: TSelf must be the first type parameter + this int CompareTo(TSelf other) where TSelf : IComparable; } ``` ### Grammar changes -The grammar for interface members is extended to allow the `this` modifier: +The grammar for interface members is extended to allow the `this` modifier with a type parameter: ```diff interface_method_declaration - : attributes? 'new'? ('abstract' | 'virtual' | 'sealed')? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* ';' -+ : attributes? 'new'? ('abstract' | 'virtual' | 'sealed' | 'this')? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* (';' | method_body) ++ : attributes? 'new'? ('abstract' | 'virtual' | 'sealed' | 'this' type_parameter_list)? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* (';' | method_body) ; ``` ### Semantic rules -When `this` is applied to an interface member: +When `this` is applied to an interface member: -1. The interface must have a first type parameter with a recursive constraint to the containing interface -2. Within the member body, `this` has the type of that self-constrained type parameter (not the interface type) +1. The type parameter `TSelf` must be constrained to the containing interface +2. Within the member body, `this` has the type `TSelf` (not the interface type) 3. The receiver is always passed by reference - For structs, this avoids boxing - For classes, the caller passes a reference to a temporary local copy of the reference variable (to avoid mutating the caller's variable if the method reassigns `this`) @@ -108,7 +108,7 @@ When `this` is applied to an interface member: ### Lowering -The `this` modifier is lowered to a static virtual method with an explicit receiver parameter. However, the member is **treated as an instance method on the containing interface** for lookup and invocation purposes. This means: +The `this` modifier is lowered to a static virtual method with an explicit receiver parameter. However, the member is **treated as an instance method on the containing interface** for lookup and invocation purposes. This means: - The method can be invoked using instance method syntax on values of the implementing type - The receiver is always passed by reference, regardless of whether it's a struct or class @@ -119,14 +119,14 @@ The `this` modifier is lowered to a static virtual method with an explicit recei #### Source code ```csharp -interface ICounter where TSelf : ICounter +interface ICounter { int Count { get; set; } - this void Increment() => Count++; + this void Increment() where TSelf : ICounter => Count++; } -struct Counter : ICounter +struct Counter : ICounter { public int Count { get; set; } // Uses default Increment() implementation @@ -138,21 +138,21 @@ struct Counter : ICounter The compiler transforms the above into: ```csharp -interface ICounter where TSelf : ICounter +interface ICounter { int Count { get; set; } - static virtual void Increment(ref TSelf @this) => @this.Count++; + static virtual void Increment(ref TSelf @this) where TSelf : ICounter => @this.Count++; } -struct Counter : ICounter +struct Counter : ICounter { public int Count { get; set; } // Uses default Increment() implementation } ``` -The key insight is that the `this` modifier provides a simpler syntax that lowers to static virtual methods with explicit receiver parameters. The compiler generates the receiver parameter automatically. +The key insight is that the `this` modifier provides a simpler syntax that lowers to static virtual methods with explicit receiver parameters. The compiler generates the receiver parameter automatically and moves the type parameter to the method signature. ### Usage @@ -168,23 +168,23 @@ The call to `c.Increment()` is resolved by the compiler as if `Increment` were a ### Signature collision rules -Because `this` members are treated as instance methods on the interface, signature collision rules apply. A `this` member with the same name and parameter types as an instance method is an error: +Because `this` members are treated as instance methods on the interface, signature collision rules apply. A `this` member with the same name and parameter types as an instance method is an error: ```csharp -interface IExample where TSelf : IExample +interface IExample { - void Foo(); // Instance method with signature Foo() - this void Foo(); // Error: collision with Foo() + void Foo(); // Instance method with signature Foo() + this void Foo() where TSelf : IExample; // Error: collision with Foo() } ``` Members with different parameters are allowed: ```csharp -interface IExample where TSelf : IExample +interface IExample { - void Bar(); // Instance method Bar() - this void Bar(int value); // OK: signature is Bar(int) + void Bar(); // Instance method Bar() + this void Bar(int value) where TSelf : IExample; // OK: signature is Bar(int) } ``` @@ -192,47 +192,47 @@ This rule ensures that method resolution is unambiguous when invoking members on ### Properties and indexers -The `this` modifier can also be applied to properties and indexers: +The `this` modifier can also be applied to properties and indexers: ```csharp -interface IHasValue where TSelf : IHasValue +interface IHasValue { - this int Value { get; } + this int Value where TSelf : IHasValue { get; } } // Lowered equivalent: -interface IHasValue where TSelf : IHasValue +interface IHasValue { - static virtual int get_Value(ref TSelf @this); + static virtual int get_Value(ref TSelf @this) where TSelf : IHasValue; } ``` ### Implementation in structs -When a struct implements an interface with `this` members, it can provide an implementation using explicit interface implementation: +When a struct implements an interface with `this` members, the default implementation is used automatically. Custom implementations can be provided using explicit interface implementation with the lowered static virtual method signature: ```csharp -struct Counter : ICounter +struct Counter : ICounter { public int Count { get; set; } - // Explicit implementation of the this member - static void ICounter.Increment(ref Counter @this) => @this.Count += 2; // Custom increment + // Custom implementation of the this member + static void ICounter.Increment(ref TSelf @this) => @this.Count += 2; // Custom increment } ``` ### Default implementations -If a `this` member has a body, that body serves as the default implementation: +If a `this` member has a body, that body serves as the default implementation: ```csharp -interface ICloneable where TSelf : ICloneable +interface ICloneable { // Abstract - no default implementation - this TSelf Clone(); + this TSelf Clone() where TSelf : ICloneable; // Has default implementation - can call other 'this' members directly - this TSelf CloneAndModify(Action modify) + this TSelf CloneAndModify(Action modify) where TSelf : ICloneable { var clone = Clone(); // Calls this.Clone() implicitly modify(clone); @@ -245,10 +245,9 @@ interface ICloneable where TSelf : ICloneable ### Complexity -- Introduces a new `this` modifier that may be confusing to developers unfamiliar with the feature +- Introduces a new `this` modifier that may be confusing to developers unfamiliar with the feature - The compiler must treat these members as instance methods for resolution purposes while emitting static virtual methods, adding implementation complexity -- Developers must understand the difference between regular default interface methods and `this` members -- Requires understanding the type parameter requirements (first parameter, recursive constraint) +- Developers must understand the difference between regular default interface methods and `this` members ### Breaking changes @@ -257,7 +256,7 @@ interface ICloneable where TSelf : ICloneable ### Interop concerns - The lowered form uses existing CLR features (static virtual methods), so runtime support should not be an issue -- Languages that don't understand the `this` semantic treatment may see these as regular static virtual methods and require explicit static invocation syntax +- Languages that don't understand the `this` semantic treatment may see these as regular static virtual methods and require explicit static invocation syntax ## Alternatives @@ -266,38 +265,38 @@ interface ICloneable where TSelf : ICloneable Developers can already achieve this behavior manually using static virtual methods and [C# 14 extension members](csharp-14.0/extensions.md): ```csharp -interface ICounter where TSelf : ICounter +interface ICounter { int Count { get; set; } - static virtual void Increment(ref TSelf @this) => @this.Count++; + static virtual void Increment(ref TSelf @this) where TSelf : ICounter => @this.Count++; } static class ICounterExt { - extension(ref T @this) where T : struct, ICounter + extension(ref T @this) where T : struct, ICounter { - public void Increment() => T.Increment(ref @this); + public void Increment() => ICounter.Increment(ref @this); } } ``` -However, this requires significant boilerplate and is error-prone. The proposed `this` modifier achieves the same semantics without requiring explicit extension method definitions. +However, this requires significant boilerplate and is error-prone. The proposed `this` modifier achieves the same semantics without requiring explicit extension method definitions. -### Using `static this` with explicit receiver +### Using interface-level type parameter -An alternative syntax could require an explicit receiver parameter: +An alternative syntax could require the interface to have a type parameter: ```csharp interface ICounter where TSelf : ICounter { int Count { get; set; } - static this void Increment(ref TSelf @this) => @this.Count++; + this void Increment() => Count++; } ``` -This was considered but rejected in favor of the simpler implicit receiver syntax, which is more consistent with how instance methods work. +This was considered but rejected because it forces the interface to be generic even when it doesn't need to be, and requires implementing types to specify themselves as the type argument (e.g., `struct Counter : ICounter`). ### Do nothing @@ -307,9 +306,9 @@ Developers could continue using the manual pattern or accept boxing for default 1. **Ref kind**: Should the implicit receiver be `ref`, `in`, or `ref readonly`? The proposal currently assumes `ref` for mutability. -2. **Naming**: Is `this` the best modifier, or would alternatives like `self` or a new keyword be clearer? +2. **Naming**: Is `this` the best modifier syntax, or would alternatives like `self` or a new keyword be clearer? -3. **Lookup precedence**: When a `this` member and a true instance member could both match, what are the exact precedence rules? +3. **Lookup precedence**: When a `this` member and a true instance member could both match, what are the exact precedence rules? 4. **Explicit invocation syntax**: Should there be a way to explicitly invoke the underlying static virtual method (e.g., for cases where the implicit instance-like syntax is not desired)? From 5438aef56528ced89c83154db1fff330ee689f65 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:21:15 +0000 Subject: [PATCH 09/10] Revert to simple `this` modifier, method-level type param in lowering only Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- .../non-boxing-default-interface-methods.md | 129 +++++++++--------- 1 file changed, 61 insertions(+), 68 deletions(-) diff --git a/proposals/non-boxing-default-interface-methods.md b/proposals/non-boxing-default-interface-methods.md index be46647402..719d9a9b8d 100644 --- a/proposals/non-boxing-default-interface-methods.md +++ b/proposals/non-boxing-default-interface-methods.md @@ -4,7 +4,7 @@ Champion issue: ## Summary -Introduce a syntax for declaring default interface methods that avoid boxing when implemented by value types. This is achieved by using a `this` modifier on interface members, which provides an implicit receiver typed as the method-level type parameter, enabling non-boxing invocation on structs. +Introduce a syntax for declaring default interface methods that avoid boxing when implemented by value types. This is achieved by using a `this` modifier on interface members, which provides an implicit receiver typed as the implementing type, enabling non-boxing invocation on structs. ## Motivation @@ -41,74 +41,47 @@ This behavior is both unexpected and can cause subtle bugs. It would be benefici ### Syntax -A new `this` modifier is introduced for interface members. When applied to a method, property, or indexer, it declares a type parameter for the receiver and indicates that the member has an implicit receiver typed as that type parameter (rather than the interface type): +A new `this` modifier is introduced for interface members. When applied to a method, property, or indexer, it indicates that the member has an implicit receiver typed as the implementing type (rather than the interface type): ```csharp interface ICounter { int Count { get; set; } - this void Increment() where TSelf : ICounter => Count++; + this void Increment() => Count++; } ``` -The `this` modifier: -- Declares a type parameter `TSelf` that represents the receiver type -- The type parameter must be constrained to the containing interface -- Provides an implicit receiver of type `TSelf` within the method body +The `this` modifier: +- Provides an implicit receiver typed as the implementing type within the method body - Takes the place of `virtual`/`abstract` modifiers - Whether the member is abstract or has a default implementation is inferred based on whether a body is provided -### Type parameter requirements - -The type parameter on the `this` modifier must be constrained to the containing interface: - -```csharp -// Valid: TSelf is constrained to IExample -interface IExample -{ - this void M() where TSelf : IExample; -} - -// Invalid: no constraint to the containing interface -interface IInvalid -{ - this void M(); // Error: TSelf must be constrained to IInvalid -} - -// Valid: additional constraints are allowed -interface IComparable -{ - this int CompareTo(TSelf other) where TSelf : IComparable; -} -``` - ### Grammar changes -The grammar for interface members is extended to allow the `this` modifier with a type parameter: +The grammar for interface members is extended to allow the `this` modifier: ```diff interface_method_declaration - : attributes? 'new'? ('abstract' | 'virtual' | 'sealed')? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* ';' -+ : attributes? 'new'? ('abstract' | 'virtual' | 'sealed' | 'this' type_parameter_list)? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* (';' | method_body) ++ : attributes? 'new'? ('abstract' | 'virtual' | 'sealed' | 'this')? return_type identifier type_parameter_list? '(' formal_parameter_list? ')' type_parameter_constraints_clause* (';' | method_body) ; ``` ### Semantic rules -When `this` is applied to an interface member: +When `this` is applied to an interface member: -1. The type parameter `TSelf` must be constrained to the containing interface -2. Within the member body, `this` has the type `TSelf` (not the interface type) -3. The receiver is always passed by reference +1. Within the member body, `this` has the type of the implementing type (not the interface type) +2. The receiver is always passed by reference - For structs, this avoids boxing - For classes, the caller passes a reference to a temporary local copy of the reference variable (to avoid mutating the caller's variable if the method reassigns `this`) -4. The member is lowered to a static virtual method with an explicit receiver parameter -5. Implementations can override this member using explicit interface implementation +3. The member is lowered to a static virtual method with a method-level type parameter and an explicit receiver parameter +4. Implementations can override this member using explicit interface implementation ### Lowering -The `this` modifier is lowered to a static virtual method with an explicit receiver parameter. However, the member is **treated as an instance method on the containing interface** for lookup and invocation purposes. This means: +The `this` modifier is lowered to a static virtual method with a method-level type parameter and an explicit receiver parameter. However, the member is **treated as an instance method on the containing interface** for lookup and invocation purposes. This means: - The method can be invoked using instance method syntax on values of the implementing type - The receiver is always passed by reference, regardless of whether it's a struct or class @@ -123,7 +96,7 @@ interface ICounter { int Count { get; set; } - this void Increment() where TSelf : ICounter => Count++; + this void Increment() => Count++; } struct Counter : ICounter @@ -135,7 +108,7 @@ struct Counter : ICounter #### Lowered equivalent -The compiler transforms the above into: +The compiler transforms the above into a static virtual method with a method-level type parameter: ```csharp interface ICounter @@ -152,7 +125,7 @@ struct Counter : ICounter } ``` -The key insight is that the `this` modifier provides a simpler syntax that lowers to static virtual methods with explicit receiver parameters. The compiler generates the receiver parameter automatically and moves the type parameter to the method signature. +The key insight is that the `this` modifier provides a simple syntax that lowers to static virtual methods with method-level type parameters and explicit receiver parameters. The compiler generates the type parameter and receiver parameter automatically. ### Usage @@ -168,13 +141,13 @@ The call to `c.Increment()` is resolved by the compiler as if `Increment` were a ### Signature collision rules -Because `this` members are treated as instance methods on the interface, signature collision rules apply. A `this` member with the same name and parameter types as an instance method is an error: +Because `this` members are treated as instance methods on the interface, signature collision rules apply. A `this` member with the same name and parameter types as an instance method is an error: ```csharp interface IExample { - void Foo(); // Instance method with signature Foo() - this void Foo() where TSelf : IExample; // Error: collision with Foo() + void Foo(); // Instance method with signature Foo() + this void Foo(); // Error: collision with Foo() } ``` @@ -183,8 +156,8 @@ Members with different parameters are allowed: ```csharp interface IExample { - void Bar(); // Instance method Bar() - this void Bar(int value) where TSelf : IExample; // OK: signature is Bar(int) + void Bar(); // Instance method Bar() + this void Bar(int value); // OK: signature is Bar(int) } ``` @@ -192,12 +165,12 @@ This rule ensures that method resolution is unambiguous when invoking members on ### Properties and indexers -The `this` modifier can also be applied to properties and indexers: +The `this` modifier can also be applied to properties and indexers: ```csharp interface IHasValue { - this int Value where TSelf : IHasValue { get; } + this int Value { get; } } // Lowered equivalent: @@ -209,45 +182,50 @@ interface IHasValue ### Implementation in structs -When a struct implements an interface with `this` members, the default implementation is used automatically. Custom implementations can be provided using explicit interface implementation with the lowered static virtual method signature: +When a struct implements an interface with `this` members, the default implementation is used automatically. Custom implementations can be provided using explicit interface implementation with the lowered static virtual method signature: ```csharp struct Counter : ICounter { public int Count { get; set; } - // Custom implementation of the this member + // Custom implementation of the this member static void ICounter.Increment(ref TSelf @this) => @this.Count += 2; // Custom increment } ``` ### Default implementations -If a `this` member has a body, that body serves as the default implementation: +If a `this` member has a body, that body serves as the default implementation: ```csharp -interface ICloneable +interface ICounter { - // Abstract - no default implementation - this TSelf Clone() where TSelf : ICloneable; + int Count { get; set; } - // Has default implementation - can call other 'this' members directly - this TSelf CloneAndModify(Action modify) where TSelf : ICloneable + // Has default implementation + this void Increment() => Count++; + + // Can call other 'this' members + this void IncrementBy(int amount) { - var clone = Clone(); // Calls this.Clone() implicitly - modify(clone); - return clone; + for (int i = 0; i < amount; i++) + { + Increment(); // Calls this.Increment() + } } } ``` +Within the body of a `this` member, the `this` keyword refers to the implementing type, not the interface type. The compiler handles the type parameter in the lowering. + ## Drawbacks ### Complexity -- Introduces a new `this` modifier that may be confusing to developers unfamiliar with the feature -- The compiler must treat these members as instance methods for resolution purposes while emitting static virtual methods, adding implementation complexity -- Developers must understand the difference between regular default interface methods and `this` members +- Introduces a new `this` modifier that may be confusing to developers unfamiliar with the feature +- The compiler must treat these members as instance methods for resolution purposes while emitting static virtual methods with method-level type parameters, adding implementation complexity +- Developers must understand the difference between regular default interface methods and `this` members ### Breaking changes @@ -256,7 +234,7 @@ interface ICloneable ### Interop concerns - The lowered form uses existing CLR features (static virtual methods), so runtime support should not be an issue -- Languages that don't understand the `this` semantic treatment may see these as regular static virtual methods and require explicit static invocation syntax +- Languages that don't understand the `this` semantic treatment may see these as regular static virtual methods and require explicit static invocation syntax ## Alternatives @@ -281,7 +259,7 @@ static class ICounterExt } ``` -However, this requires significant boilerplate and is error-prone. The proposed `this` modifier achieves the same semantics without requiring explicit extension method definitions. +However, this requires significant boilerplate and is error-prone. The proposed `this` modifier achieves the same semantics without requiring explicit extension method definitions. ### Using interface-level type parameter @@ -298,6 +276,21 @@ interface ICounter where TSelf : ICounter This was considered but rejected because it forces the interface to be generic even when it doesn't need to be, and requires implementing types to specify themselves as the type argument (e.g., `struct Counter : ICounter`). +### Using method-level type parameter in syntax + +An alternative syntax could expose the type parameter in the source: + +```csharp +interface ICounter +{ + int Count { get; set; } + + this void Increment() where TSelf : ICounter => Count++; +} +``` + +This was considered but rejected in favor of the simpler `this` modifier, with the type parameter being an implementation detail of the lowering. + ### Do nothing Developers could continue using the manual pattern or accept boxing for default interface methods on structs. However, the boilerplate required is substantial and the boxing behavior causes subtle bugs. @@ -306,9 +299,9 @@ Developers could continue using the manual pattern or accept boxing for default 1. **Ref kind**: Should the implicit receiver be `ref`, `in`, or `ref readonly`? The proposal currently assumes `ref` for mutability. -2. **Naming**: Is `this` the best modifier syntax, or would alternatives like `self` or a new keyword be clearer? +2. **Naming**: Is `this` the best modifier, or would alternatives like `self` or a new keyword be clearer? -3. **Lookup precedence**: When a `this` member and a true instance member could both match, what are the exact precedence rules? +3. **Lookup precedence**: When a `this` member and a true instance member could both match, what are the exact precedence rules? 4. **Explicit invocation syntax**: Should there be a way to explicitly invoke the underlying static virtual method (e.g., for cases where the implicit instance-like syntax is not desired)? From 5dfd6d828f7e63c19a34b63a879e266048eae6a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:23:31 +0000 Subject: [PATCH 10/10] Clarify: receiver is anonymous type parameter, by-ref Co-authored-by: agocke <515774+agocke@users.noreply.github.com> --- proposals/non-boxing-default-interface-methods.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/proposals/non-boxing-default-interface-methods.md b/proposals/non-boxing-default-interface-methods.md index 719d9a9b8d..5eddc3580d 100644 --- a/proposals/non-boxing-default-interface-methods.md +++ b/proposals/non-boxing-default-interface-methods.md @@ -73,18 +73,18 @@ The grammar for interface members is extended to allow the `this` modifier: When `this` is applied to an interface member: 1. Within the member body, `this` has the type of the implementing type (not the interface type) -2. The receiver is always passed by reference +2. The receiver is an anonymous type parameter constrained to the containing interface, passed by reference - For structs, this avoids boxing - For classes, the caller passes a reference to a temporary local copy of the reference variable (to avoid mutating the caller's variable if the method reassigns `this`) -3. The member is lowered to a static virtual method with a method-level type parameter and an explicit receiver parameter +3. The member is lowered to a static virtual method with an anonymous type parameter and a by-ref receiver parameter 4. Implementations can override this member using explicit interface implementation ### Lowering -The `this` modifier is lowered to a static virtual method with a method-level type parameter and an explicit receiver parameter. However, the member is **treated as an instance method on the containing interface** for lookup and invocation purposes. This means: +The `this` modifier is lowered to a static virtual method with an anonymous type parameter constrained to the containing interface, and a by-ref receiver parameter. However, the member is **treated as an instance method on the containing interface** for lookup and invocation purposes. This means: - The method can be invoked using instance method syntax on values of the implementing type -- The receiver is always passed by reference, regardless of whether it's a struct or class +- The receiver is an anonymous type parameter constrained to the containing interface, passed by reference - For structs, this avoids boxing - For classes, the caller passes a reference to a temporary local copy of the reference variable (to avoid mutating the caller's variable if the method reassigns `this`) - Method resolution treats these members as instance methods, even though the underlying implementation uses static virtual methods @@ -108,7 +108,7 @@ struct Counter : ICounter #### Lowered equivalent -The compiler transforms the above into a static virtual method with a method-level type parameter: +The compiler transforms the above into a static virtual method with an anonymous type parameter constrained to the containing interface and a by-ref receiver parameter: ```csharp interface ICounter @@ -125,7 +125,7 @@ struct Counter : ICounter } ``` -The key insight is that the `this` modifier provides a simple syntax that lowers to static virtual methods with method-level type parameters and explicit receiver parameters. The compiler generates the type parameter and receiver parameter automatically. +The key insight is that the `this` modifier provides a simple syntax that lowers to static virtual methods with an anonymous type parameter and a by-ref receiver parameter. The compiler generates these automatically. ### Usage @@ -224,7 +224,7 @@ Within the body of a `this` member, the `this` keyword refers to the implementin ### Complexity - Introduces a new `this` modifier that may be confusing to developers unfamiliar with the feature -- The compiler must treat these members as instance methods for resolution purposes while emitting static virtual methods with method-level type parameters, adding implementation complexity +- The compiler must treat these members as instance methods for resolution purposes while emitting static virtual methods with anonymous type parameters, adding implementation complexity - Developers must understand the difference between regular default interface methods and `this` members ### Breaking changes