From b45358ad4304e9037989bcc976925aa9c4828d88 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 23 Jan 2026 05:49:02 -0500 Subject: [PATCH 1/4] 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 b4b75ec21..21b03f214 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 f56da6e84420aadb18c2b75a98ae9f2b68499a63 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 23 Jan 2026 05:52:05 -0500 Subject: [PATCH 2/4] 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 5c46468f3..fa74a9a04 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -785,7 +785,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 bdf236664f780eee38957734581547c3d2d30613 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 23 Jan 2026 06:02:09 -0500 Subject: [PATCH 3/4] 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 21b03f214..b941c9c68 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 c64437b9246f98dc82682c30f0be13d221ef6a9d Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 10 Feb 2026 14:47:15 -0500 Subject: [PATCH 4/4] 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 b941c9c68..fee46b164 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))]