From 2e9d79a1fe9d11702c95f44f221a73e19fc6eb42 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 23 Jan 2026 05:49:02 -0500 Subject: [PATCH 1/6] Add support for AsyncMethodBuilder attribute --- standard/classes.md | 72 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/standard/classes.md b/standard/classes.md index e2ee11ab2..ae9ac575d 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -5886,6 +5886,78 @@ When a function is implemented using an iterator block, it is a compile-time err An asynchronous iterator shall support cancellation of the asynchronous operation. This is described in [§23.5.8](attributes.md#2358-the-enumeratorcancellation-attribute). +Rather than using a task builder type based on the *return_type* of an async method, the attribute `AsyncMethodBuilder` can be applied to that method to indicate a different task builder type. + +It is an error to apply this attribute to a lambda with an implicit return type. + +The ability to provide an alternate builder type shall not be used when the synthesized entry-point for top-level statements is async ([§7.1.3](basic-concepts.md#713-using-top-level-statements)). For that, an explicit entry-point is needed. + +When an async method is compiled, the builder type is determined by: + +1. Using the builder type from the `AsyncMethodBuilder` attribute, if one is present; +1. Otherwise, falling back to the builder type determined by the method’s *return_type*. + +If an `AsyncMethodBuilder` attribute is present, the builder type specified by that attribute is constructed, if necessary. + +If the override type is an open generic type, take the single type argument of the async method's return type and substitute it into the override type. + +If the override type is a bound generic type, then an error results. + +If the async method's return type does not have a single type argument, an error results. + +To verify that the builder type is compatible with *return_type* of the async method: + +1. Look for the public `Create` method with no type parameters and no parameters on the constructed builder type. It is an error if the method is not found, or if the method returns a type other than the constructed builder type. +1. Look for the public `Task` property. It is an error if the property is not found. +1. Consider the type of that `Task` property (a task-like type). It is an error if the task-like type does not match the *return_type* of the async method. (It is not necessary for *return_type* to be a task-like type.) + +Consider the following code fragment: + +```csharp +public async ValueTask ExampleAsync() { … } +``` + +This will be compiled to something like the following: + +```C# +[AsyncStateMachine(typeof(d__29))] +[CompilerGenerated] +static ValueTask ExampleAsync() +{ + d__29 stateMachine; + stateMachine.<>t__builder + = AsyncValueTaskMethodBuilder.Create(); + stateMachine.<>1__state = -1; + stateMachine.<>t__builder.Start(ref stateMachine); + return stateMachine.<>t__builder.Task; +} +``` + +However, the following code fragment: + +```C# +[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] +static async ValueTask ExampleAsync() { … } +``` + +in which the attribute `AsyncMethodBuilder ` is applied to that method, would instead be compiled to something like: + +```C# +[AsyncStateMachine(typeof(d__29))] +[CompilerGenerated] +[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] +static ValueTask ExampleAsync() +{ + d__29 stateMachine; + stateMachine.<>t__builder + = PoolingAsyncValueTaskMethodBuilder.Create(); + // <>t__builder now a different type + stateMachine.<>1__state = -1; + stateMachine.<>t__builder.Start(ref stateMachine); + return stateMachine.<>t__builder.Task; +} +``` + ### 15.15.2 Enumerator interfaces The ***enumerator interface***s are the non-generic interface `System.Collections.IEnumerator` and the generic interface `System.Collections.Generic.IEnumerator`. From 16ccbafed6a7a0a6bfb892a2302cd33b7f408854 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 23 Jan 2026 05:52:05 -0500 Subject: [PATCH 2/6] Support AsyncMethodBuilder attribute --- standard/standard-library.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/standard-library.md b/standard/standard-library.md index 4fa2d44b1..c63b16892 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -780,7 +780,7 @@ namespace System.Linq.Expressions namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | - AttributeTargets.Interface, + AttributeTargets.Interface | AttributeTargets.Method, Inherited = false, AllowMultiple = false)] public sealed class AsyncMethodBuilderAttribute : Attribute { From 702167b68f7c67ad0c3012987189b9c20f22e495 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 23 Jan 2026 06:02:09 -0500 Subject: [PATCH 3/6] fix md formatting --- standard/classes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/classes.md b/standard/classes.md index ae9ac575d..6e792a522 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -5919,7 +5919,7 @@ public async ValueTask ExampleAsync() { … } This will be compiled to something like the following: -```C# +```csharp [AsyncStateMachine(typeof(d__29))] [CompilerGenerated] static ValueTask ExampleAsync() @@ -5935,14 +5935,14 @@ static ValueTask ExampleAsync() However, the following code fragment: -```C# +```csharp [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] static async ValueTask ExampleAsync() { … } ``` in which the attribute `AsyncMethodBuilder ` is applied to that method, would instead be compiled to something like: -```C# +```csharp [AsyncStateMachine(typeof(d__29))] [CompilerGenerated] [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] From 4e004fd504e43da74f429bbbdfa5e9fd55573a65 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 10 Feb 2026 14:47:15 -0500 Subject: [PATCH 4/6] Apply suggestion from @BillWagner --- standard/classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/classes.md b/standard/classes.md index 6e792a522..13ec1503b 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -5940,7 +5940,7 @@ However, the following code fragment: static async ValueTask ExampleAsync() { … } ``` -in which the attribute `AsyncMethodBuilder ` is applied to that method, would instead be compiled to something like: +in which the attribute `AsyncMethodBuilder` is applied to that method, would instead be compiled to something like: ```csharp [AsyncStateMachine(typeof(d__29))] From 776ac498baa6b09e736de204ba30d3976eb94ca3 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 24 Mar 2026 14:06:37 -0400 Subject: [PATCH 5/6] Move new section I think it was put in the wrong spot in an earlier merge. --- standard/classes.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/standard/classes.md b/standard/classes.md index 13ec1503b..a7940ca15 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -5872,30 +5872,16 @@ If the return type of the async function is `void`, evaluation differs from the This allows the context to keep track of how many `void`-returning async functions are running under it, and to decide how to propagate exceptions coming out of them. -## 15.15 Synchronous and asynchronous iterators - -### 15.15.1 General - -A function member ([§12.6](expressions.md#126-function-members)) or local function ([§13.6.4](statements.md#1364-local-function-declarations)) implemented using an iterator block ([§13.3](statements.md#133-blocks)) is called an ***iterator***. An iterator block may be used as the body of a function as long as the return type of the corresponding function is one of the enumerator interfaces ([§15.15.2](classes.md#15152-enumerator-interfaces)) or one of the enumerable interfaces ([§15.15.3](classes.md#15153-enumerable-interfaces)). - -An async function ([§15.14](classes.md#1514-async-functions)) or local function ([§13.6.4](statements.md#1364-local-function-declarations)) implemented using an iterator block ([§13.3](statements.md#133-blocks)) is called an ***asynchronous iterator***. An asynchronous iterator block may be used as the body of a function as long as the return type of the corresponding function is the asynchronous enumerator interface ([§15.15.2](classes.md#15152-enumerator-interfaces)) or the asynchronous enumerable interface ([§15.15.3](classes.md#15153-enumerable-interfaces)). - -An iterator block may occur as a *method_body*, *operator_body* or *accessor_body*, whereas events, instance constructors, static constructors and finalizer shall not be implemented as synchronous or asynchronous iterators. - -When a function is implemented using an iterator block, it is a compile-time error for the parameter list of the function to specify any `in`, `out`, or `ref` parameters, or a parameter of a `ref struct` type. - -An asynchronous iterator shall support cancellation of the asynchronous operation. This is described in [§23.5.8](attributes.md#2358-the-enumeratorcancellation-attribute). - Rather than using a task builder type based on the *return_type* of an async method, the attribute `AsyncMethodBuilder` can be applied to that method to indicate a different task builder type. It is an error to apply this attribute to a lambda with an implicit return type. -The ability to provide an alternate builder type shall not be used when the synthesized entry-point for top-level statements is async ([§7.1.3](basic-concepts.md#713-using-top-level-statements)). For that, an explicit entry-point is needed. +The ability to provide an alternate builder type shall not be used when the synthesized entry-point for top-level statements is async (§7.1.3). For that, an explicit entry-point is needed. When an async method is compiled, the builder type is determined by: 1. Using the builder type from the `AsyncMethodBuilder` attribute, if one is present; -1. Otherwise, falling back to the builder type determined by the method’s *return_type*. +1. Otherwise, falling back to the builder type determined by the method's *return_type*. If an `AsyncMethodBuilder` attribute is present, the builder type specified by that attribute is constructed, if necessary. @@ -5958,6 +5944,20 @@ static ValueTask ExampleAsync() } ``` +## 15.15 Synchronous and asynchronous iterators + +### 15.15.1 General + +A function member ([§12.6](expressions.md#126-function-members)) or local function ([§13.6.4](statements.md#1364-local-function-declarations)) implemented using an iterator block ([§13.3](statements.md#133-blocks)) is called an ***iterator***. An iterator block may be used as the body of a function as long as the return type of the corresponding function is one of the enumerator interfaces ([§15.15.2](classes.md#15152-enumerator-interfaces)) or one of the enumerable interfaces ([§15.15.3](classes.md#15153-enumerable-interfaces)). + +An async function ([§15.14](classes.md#1514-async-functions)) or local function ([§13.6.4](statements.md#1364-local-function-declarations)) implemented using an iterator block ([§13.3](statements.md#133-blocks)) is called an ***asynchronous iterator***. An asynchronous iterator block may be used as the body of a function as long as the return type of the corresponding function is the asynchronous enumerator interface ([§15.15.2](classes.md#15152-enumerator-interfaces)) or the asynchronous enumerable interface ([§15.15.3](classes.md#15153-enumerable-interfaces)). + +An iterator block may occur as a *method_body*, *operator_body* or *accessor_body*, whereas events, instance constructors, static constructors and finalizer shall not be implemented as synchronous or asynchronous iterators. + +When a function is implemented using an iterator block, it is a compile-time error for the parameter list of the function to specify any `in`, `out`, or `ref` parameters, or a parameter of a `ref struct` type. + +An asynchronous iterator shall support cancellation of the asynchronous operation. This is described in [§23.5.8](attributes.md#2358-the-enumeratorcancellation-attribute). + ### 15.15.2 Enumerator interfaces The ***enumerator interface***s are the non-generic interface `System.Collections.IEnumerator` and the generic interface `System.Collections.Generic.IEnumerator`. From e2fd4cfb5960415ee62bc27c43f05ed6a4c7572e Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 24 Mar 2026 14:13:02 -0400 Subject: [PATCH 6/6] Grammar and formatting Replace "async method" with "async function" This feature applies to local functions and lambda expressions as well. Put examples in an informative *Example* block Update cross references as needed --- standard/classes.md | 108 ++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/standard/classes.md b/standard/classes.md index a7940ca15..802a8387b 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -5872,77 +5872,79 @@ If the return type of the async function is `void`, evaluation differs from the This allows the context to keep track of how many `void`-returning async functions are running under it, and to decide how to propagate exceptions coming out of them. -Rather than using a task builder type based on the *return_type* of an async method, the attribute `AsyncMethodBuilder` can be applied to that method to indicate a different task builder type. +Rather than using a task builder type based on the *return_type* of an async function, the attribute `AsyncMethodBuilder` can be applied to that async function to indicate a different task builder type. It is an error to apply this attribute to a lambda with an implicit return type. The ability to provide an alternate builder type shall not be used when the synthesized entry-point for top-level statements is async (§7.1.3). For that, an explicit entry-point is needed. -When an async method is compiled, the builder type is determined by: +When an async function is compiled, the builder type is determined by: 1. Using the builder type from the `AsyncMethodBuilder` attribute, if one is present; -1. Otherwise, falling back to the builder type determined by the method's *return_type*. +1. Otherwise, falling back to the builder type determined by the async function's *return_type* (§15.14.2). If an `AsyncMethodBuilder` attribute is present, the builder type specified by that attribute is constructed, if necessary. -If the override type is an open generic type, take the single type argument of the async method's return type and substitute it into the override type. +If the override type is an open generic type, take the single type argument of the async function's return type and substitute it into the override type. If the override type is a bound generic type, then an error results. -If the async method's return type does not have a single type argument, an error results. +If the async function's return type does not have a single type argument, an error results. -To verify that the builder type is compatible with *return_type* of the async method: +To verify that the builder type is compatible with *return_type* of the async function: 1. Look for the public `Create` method with no type parameters and no parameters on the constructed builder type. It is an error if the method is not found, or if the method returns a type other than the constructed builder type. 1. Look for the public `Task` property. It is an error if the property is not found. -1. Consider the type of that `Task` property (a task-like type). It is an error if the task-like type does not match the *return_type* of the async method. (It is not necessary for *return_type* to be a task-like type.) +1. Consider the type of that `Task` property (a task-like type). It is an error if the task-like type does not match the *return_type* of the async function. (It is not necessary for *return_type* to be a task-like type.) -Consider the following code fragment: - -```csharp -public async ValueTask ExampleAsync() { … } -``` - -This will be compiled to something like the following: - -```csharp -[AsyncStateMachine(typeof(d__29))] -[CompilerGenerated] -static ValueTask ExampleAsync() -{ - d__29 stateMachine; - stateMachine.<>t__builder - = AsyncValueTaskMethodBuilder.Create(); - stateMachine.<>1__state = -1; - stateMachine.<>t__builder.Start(ref stateMachine); - return stateMachine.<>t__builder.Task; -} -``` - -However, the following code fragment: - -```csharp -[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] -static async ValueTask ExampleAsync() { … } -``` - -in which the attribute `AsyncMethodBuilder` is applied to that method, would instead be compiled to something like: - -```csharp -[AsyncStateMachine(typeof(d__29))] -[CompilerGenerated] -[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] -static ValueTask ExampleAsync() -{ - d__29 stateMachine; - stateMachine.<>t__builder - = PoolingAsyncValueTaskMethodBuilder.Create(); - // <>t__builder now a different type - stateMachine.<>1__state = -1; - stateMachine.<>t__builder.Start(ref stateMachine); - return stateMachine.<>t__builder.Task; -} -``` +> *Example*: Consider the following code fragment: +> +> ```csharp +> public async ValueTask ExampleAsync() { … } +> ``` +> +> This will be compiled to something like the following: +> +> ```csharp +> [AsyncStateMachine(typeof(d__29))] +> [CompilerGenerated] +> static ValueTask ExampleAsync() +> { +> d__29 stateMachine; +> stateMachine.<>t__builder +> = AsyncValueTaskMethodBuilder.Create(); +> stateMachine.<>1__state = -1; +> stateMachine.<>t__builder.Start(ref stateMachine); +> return stateMachine.<>t__builder.Task; +> } +> ``` +> +> However, the following code fragment: +> +> ```csharp +> [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] +> static async ValueTask ExampleAsync() { … } +> ``` +> +> in which the attribute `AsyncMethodBuilder` is applied to that async function, would instead be compiled to something like: +> +> ```csharp +> [AsyncStateMachine(typeof(d__29))] +> [CompilerGenerated] +> [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] +> static ValueTask ExampleAsync() +> { +> d__29 stateMachine; +> stateMachine.<>t__builder +> = PoolingAsyncValueTaskMethodBuilder.Create(); +> // <>t__builder now a different type +> stateMachine.<>1__state = -1; +> stateMachine.<>t__builder.Start(ref stateMachine); +> return stateMachine.<>t__builder.Task; +> } +> ``` +> +> *end example* ## 15.15 Synchronous and asynchronous iterators