Skip to content

Commit f39791f

Browse files
RexJaeschkeBillWagnerNigel-Ecmajskeetjnm2
authored
Add support for using declarations and async using (#672)
* add support or using declarations and async using * Add IAsyncDisposable to the library * Update statements.md * Add example annotation * add annotation for examples * Create SupportLocalVarDecl.cs * fix section number * fix links * Update statements.md * fix header label * describe async using expansion Include the rule that the result of `DisposeAsync` must be `await`ed. * Update standard/statements.md Co-authored-by: Nigel-Ecma <[email protected]> * Address language in review comments This commit address the comments in this review: #672 (review) except for any grammar changes. Notes on where the edit differs (slightly) from the recommendation: - The rule that a `using` statement can't declare an `out` variable applies to all forms. I added that in 13.14.1. - I added a note that ref structs can't use the form of `await using`. It's a note because it follows from the normative restriction that `ref struct` types can't implement interfaces, but `await using` requires the resource type to implement `IAsyncDisposable`. - The rules for `goto`, `break`, etc. are a note at the end of 13.14.1. They can be derived from the expanded forms of the `using` statement. - The rule for a using declaration in a switch block remains. It's specific to a using declaration. * Update grammar Update grammar based on #672 (review) * refactor foreach into common sections Refactor the common processing for `foreach` and `await foreach`. The common processing is the determination of the iteration type and such. The lowering is still split into different sections. That could be combined, but as each is specific, I'm not sure it provides a simplification. * Add attribute section for async iterator cancellation In addition, add usings to the relevant template * add normative text for async cancellation * Address feedback in #606 Post meeting, a number of reviews were left on #606 to be addressed in this PR. All but one are addressed in this commit * Clarify sync vs. async enumerable objects. * Add missing note. * address some feedback Address some of the feedback items before the upcoming meeting. * Apply suggestions from code review Co-authored-by: Jon Skeet <[email protected]> * Apply suggestions from code review Co-authored-by: Joseph Musser <[email protected]> Co-authored-by: Jon Skeet <[email protected]> * Interim checkin. * Add language on cancellation I stopped a bit short here. The more I dove into the cancellation protocol, the more it felt like this was an implementation spec, not a standard. Adding a good illustrative sample would mean showing the implementation of the async state machine for an async iterator. That seems to restricting. * Remove `using` from duplicated grammar * Apply suggestions from code review Co-authored-by: Jon Skeet <[email protected]> * Apply suggestions from code review Co-authored-by: Jon Skeet <[email protected]> * respond to feedback from July 30 meeting * Apply suggestions from code review Co-authored-by: Nigel-Ecma <[email protected]> * Edits, part 1 Respond to review from @Nigel-Ecma on 8/15, *except* the summary comment. * Address overall review comments This might still be a bit hand-wavy, but better. * Apply suggestions from code review Co-authored-by: Joseph Musser <[email protected]> * respond to feedback. * Apply suggestions from code review Co-authored-by: Joseph Musser <[email protected]> * Add async dispose specification * Update sample, fix build issue. * Apply suggestions from code review Co-authored-by: Jon Skeet <[email protected]> * remaining review comments as of 9/2 * Update standard/attributes.md Co-authored-by: Nigel-Ecma <[email protected]> * Clarify dispose and finally Clarify the semantics of DisposeAsync and Dispose for asynchronous enumerators. * respond to final feedback. * Apply suggestions from code review Co-authored-by: Nigel-Ecma <[email protected]> * Respond to feedback. Update cancellation example. Make description of `finally` clauses consistent. * Update sample and fix warnings Update the sample per Nigel & Joseph's comments. Also, update anchors that have changed due to the index and range merge. * Update standard/attributes.md Co-authored-by: Joseph Musser <[email protected]> * Update standard/attributes.md * Update standard/attributes.md * Update standard/attributes.md * fix sample * ensure sample compiles in C# 8 --------- Co-authored-by: Bill Wagner <[email protected]> Co-authored-by: Nigel-Ecma <[email protected]> Co-authored-by: Jon Skeet <[email protected]> Co-authored-by: Joseph Musser <[email protected]>
1 parent aa28a99 commit f39791f

File tree

6 files changed

+332
-136
lines changed

6 files changed

+332
-136
lines changed

standard/attributes.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ A number of attributes affect the language in some way. These attributes include
495495
- `System.ObsoleteAttribute` ([§23.5.4](attributes.md#2354-the-obsolete-attribute)), which is used to mark a member as obsolete.
496496
- `System.Runtime.CompilerServices.AsyncMethodBuilderAttribute` ([§23.5.5](attributes.md#2355-the-asyncmethodbuilder-attribute)), which is used to establish a task builder for an async method.
497497
- `System.Runtime.CompilerServices.CallerLineNumberAttribute` ([§23.5.6.2](attributes.md#23562-the-callerlinenumber-attribute)), `System.Runtime.CompilerServices.CallerFilePathAttribute` ([§23.5.6.3](attributes.md#23563-the-callerfilepath-attribute)), and `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([§23.5.6.4](attributes.md#23564-the-callermembername-attribute)), which are used to supply information about the calling context to optional parameters.
498+
- `System.Runtime.CompilerServices.EnumeratorCancellationAttribute` (§enumerator-cancellation), which is used to specify parameter for the cancellation token in an asynchronous iterator.
498499
499500
The Nullable static analysis attributes ([§23.5.7](attributes.md#2357-code-analysis-attributes)) can improve the correctness of warnings generated for nullabilities and null states ([§8.9.5](types.md#895-nullabilities-and-null-states)).
500501
@@ -1060,6 +1061,52 @@ Specifies that a nullable argument won’t be `null` when the method returns the
10601061
>
10611062
> *end example*
10621063
1064+
### §enumerator-cancellation The EnumeratorCancellation attribute
1065+
1066+
Specifies the parameter representing the `CancellationToken` for an asynchronous iterator15.15). The argument for this parameter shall be combined with the argument passed to `IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken)`. This combined token shall be polled by `IAsyncEnumerator<T>.MoveNextAsync()` (§15.15.5.2). The tokens shall be combined into a single token as if by `CancellationToken.CreateLinkedTokenSource` and its `Token` property. The combined token will be canceled if either of the two source tokens are canceled. The combined token is seen as the argument to the asynchronous iterator method15.15) in the body of that method.
1067+
1068+
It is an error if the `System.Runtime.CompilerServices.EnumeratorCancellation` attribute is applied to more than one parameter. The compiler may produce a warning if:
1069+
1070+
- The `EnumeratorCancellation` attribute is applied to a parameter of a type other than `CancellationToken`,
1071+
- or if the `EnumeratorCancellation` attribute is applied to a parameter on a method that isn't an asynchronous iterator15.15),
1072+
- or if the `EnumeratorCancellation` attribute is applied to a parameter on a method that returns an asynchronous enumerable interface ([§15.15.3](classes.md#15153-enumerable-interfaces)) rather than an asynchronous enumerator interface ([§15.15.2](classes.md#15152-enumerator-interfaces)).
1073+
1074+
The iterator won't have access to the `CancellationToken` argument for `GetAsyncEnumerator` when no attributes have this parameter.
1075+
1076+
> *Example*: The method `GetStringsAsync()` is an asynchronous iterator. Before doing any work to retrieve the next value, it checks the cancellation token to determine if the iteration should be canceled. If cancellation is requested, no further action is taken.
1077+
>
1078+
> <!-- Example: {template:"code-in-class-lib", name:"AsyncEnumeratorCancellation"} -->
1079+
> ```csharp
1080+
> public static async Task ExampleCombination()
1081+
> {
1082+
> var sourceOne = new CancellationTokenSource();
1083+
> var sourceTwo = new CancellationTokenSource();
1084+
> await using (IAsyncEnumerator<string> enumerator =
1085+
> GetStringsAsync(sourceOne.Token).GetAsyncEnumerator(sourceTwo.Token))
1086+
> {
1087+
> while (await enumerator.MoveNextAsync())
1088+
> {
1089+
> string number = enumerator.Current;
1090+
> if (number == "8") sourceOne.Cancel();
1091+
> if (number == "5") sourceTwo.Cancel();
1092+
> Console.WriteLine(number);
1093+
> }
1094+
> }
1095+
> }
1096+
>
1097+
> static async IAsyncEnumerable<string> GetStringsAsync([EnumeratorCancellation] CancellationToken token)
1098+
> {
1099+
> for (int i = 0; i < 10; i++)
1100+
> {
1101+
> if (token.IsCancellationRequested) yield break;
1102+
> await Task.Delay(1000, token);
1103+
> yield return i.ToString();
1104+
> }
1105+
> }
1106+
> ```
1107+
>
1108+
> *end example*
1109+
10631110
## 23.6 Attributes for interoperation
10641111
10651112
For interoperation with other languages, an indexer may be implemented using indexed properties. If no `IndexerName` attribute is present for an indexer, then the name `Item` is used by default. The `IndexerName` attribute enables a developer to override this default and specify a different name.

standard/classes.md

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5496,27 +5496,29 @@ This allows the context to keep track of how many `void`-returning async functio
54965496

54975497
### 15.15.1 General
54985498

5499-
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 member as long as the return type of the corresponding function member 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)).
5499+
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)).
55005500

5501-
An async function ([§15.14](classes.md#1514-async-functions)) 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 member as long as the return type of the corresponding function member is the asynchronous enumerator interfaces ([§15.15.2](classes.md#15152-enumerator-interfaces)) or the asynchronous enumerable interfaces ([§15.15.3](classes.md#15153-enumerable-interfaces)).
5501+
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)).
55025502

55035503
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.
55045504

5505-
When a function member or local function is implemented using an iterator block, it is a compile-time error for the parameter list of the function member to specify any `in`, `out`, or `ref` parameters, or an parameter of a `ref struct` type.
5505+
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.
5506+
5507+
An asynchronous iterator shall support cancellation of the asynchronous operation. This is described in §enumerator-cancellation.
55065508

55075509
### 15.15.2 Enumerator interfaces
55085510

5509-
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and all instantiations of the generic interfaces `System.Collections.Generic.IEnumerator<T>`.
5511+
The ***enumerator interfaces*** are the non-generic interface `System.Collections.IEnumerator` and the generic interface `System.Collections.Generic.IEnumerator<T>`.
55105512

5511-
The ***asynchronous enumerator interfaces*** are all instantiations of the generic interface `System.Collections.Generic.IAsyncEnumerator<T>`.
5513+
The ***asynchronous enumerator interface*** is the generic interface `System.Collections.Generic.IAsyncEnumerator<T>`.
55125514

55135515
For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerator`, `IEnumerator<T>`, and `IAsyncEnumerator<T>`, respectively.
55145516

55155517
### 15.15.3 Enumerable interfaces
55165518

5517-
The ***enumerable interfaces*** are the non-generic interface `System.Collections.IEnumerable` and all instantiations of the generic interfaces `System.Collections.Generic.IEnumerable<T>`.
5519+
The ***enumerable interfaces*** are the non-generic interface `System.Collections.IEnumerable` and the generic interfaces `System.Collections.Generic.IEnumerable<T>`.
55185520

5519-
The ***asynchronous enumerable interfaces*** are all instantiations of the generic interface `System.Collections.Generic.IAsyncEnumerable<T>`.
5521+
The ***asynchronous enumerable interface*** is the generic interface `System.Collections.Generic.IAsyncEnumerable<T>`.
55205522

55215523
For the sake of brevity, in this subclause and its siblings these interfaces are referenced as `IEnumerable`, `IEnumerable<T>`, and `IAsyncEnumerable<T>`, respectively.
55225524

@@ -5531,13 +5533,13 @@ An iterator produces a sequence of values, all of the same type. This type is ca
55315533

55325534
#### 15.15.5.1 General
55335535

5534-
When a function member or local function returning an enumerator interface type is implemented using an iterator block, invoking the function does not immediately execute the code in the iterator block. Instead, an ***enumerator object*** is created and returned. This object encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the enumerator object’s `MoveNext` or `MoveNextAsync` method is invoked. An enumerator object has the following characteristics:
5536+
When a function member or local function returning an enumerator interface type or an asynchronous enumerator interface type is implemented using an iterator block, invoking the function does not immediately execute the code in the iterator block. Instead, an ***enumerator object*** is created and returned. This object encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the enumerator object’s `MoveNext` or `MoveNextAsync` method is invoked. An enumerator object has the following characteristics:
55355537

55365538
- It implements `System.IDisposable`, `IEnumerator` and `IEnumerator<T>`, or `System.IAsyncDisposable` and `IAsyncEnumerator<T>`, where `T` is the yield type of the iterator.
5537-
- It is initialized with a copy of the argument values (if any) and instance value passed to the function member.
5539+
- It is initialized with a copy of the argument values (if any) and instance value passed to the function.
55385540
- It has four potential states, **before**, **running**, **suspended**, and **after**, and is initially in the **before** state.
55395541

5540-
An enumerator object is typically an instance of a compiler-generated enumerator class that encapsulates the code in the iterator block and implements the enumerator interfaces, but other methods of implementation are possible. If an enumerator class is generated by the compiler, that class will be nested, directly or indirectly, in the class containing the function member, it will have private accessibility, and it will have a name reserved for compiler use ([§6.4.3](lexical-structure.md#643-identifiers)).
5542+
An enumerator object is typically an instance of a compiler-generated enumerator class that encapsulates the code in the iterator block and implements the enumerator interfaces, but other methods of implementation are possible. If an enumerator class is generated by the compiler, that class will be nested, directly or indirectly, in the class containing the function, it will have private accessibility, and it will have a name reserved for compiler use ([§6.4.3](lexical-structure.md#643-identifiers)).
55415543

55425544
An enumerator object may implement more interfaces than those specified above.
55435545

@@ -5573,6 +5575,8 @@ The precise action performed by `MoveNext` or `MoveNextAsync` depends on the sta
55735575

55745576
When `MoveNext` executes the iterator block, execution can be interrupted in four ways: By a `yield return` statement, by a `yield break` statement, by encountering the end of the iterator block, and by an exception being thrown and propagated out of the iterator block.
55755577

5578+
> *Note*: `MoveNextAsync` is suspended if it evaluates an `await` expression that awaits a task type that hasn't completed. *end note*
5579+
55765580
- When a `yield return` statement is encountered ([§9.4.4.20](variables.md#94420-yield-statements)):
55775581
- The expression given in the statement is evaluated, implicitly converted to the yield type, and assigned to the `Current` property of the enumerator object.
55785582
- Execution of the iterator body is suspended. The values of all local variables and parameters (including `this`) are saved, as is the location of this `yield return` statement. If the `yield return` statement is within one or more `try` blocks, the associated finally blocks are *not* executed at this time.
@@ -5616,21 +5620,20 @@ The `Dispose` or `DisposeAsync` method is used to clean up the iteration by brin
56165620

56175621
#### 15.15.6.1 General
56185622

5619-
When a function member or local function returning an enumerable interface type is implemented using an iterator block, invoking the function member does not immediately execute the code in the iterator block. Instead, an ***enumerable object*** is created and returned.
5623+
When a function member or local function returning an enumerable interface type or an async enumerable interface type is implemented using an iterator block, invoking the function does not immediately execute the code in the iterator block. Instead, an ***enumerable object*** is created and returned.
56205624

5621-
The enumerable object’s `GetEnumerator` or `GetAsyncEnumerator` method returns an enumerator object that encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the enumerator object’s `MoveNext` or `MoveNextAsync` method is invoked. An enumerable object has the following characteristics:
5625+
A synchronous enumerable object implements `IEnumerable` and `IEnumerable<T>`, where `T` is the yield type of the iterator. Its `GetEnumerator` method returns an enumerator object (§15.15.5). An async enumerable object implements `IAsyncEnumerable<T>` where `T` is the yield type of the iterator. Its `GetAsyncEnumerator` method returns an asynchronous enumerator object (§15.15.5).
56225626

5623-
- It implements `IEnumerable` and `IEnumerable<T>` or `IAsyncEnumerable<T>`, where `T` is the yield type of the iterator.
5624-
- It is initialized with a copy of the argument values (if any) and instance value passed to the function member.
5627+
An enumerable object is initialized with a copy of the argument values (if any) and instance value passed to the function.
56255628

5626-
An enumerable object is typically an instance of a compiler-generated enumerable class that encapsulates the code in the iterator block and implements the enumerable interfaces, but other methods of implementation are possible. If an enumerable class is generated by the compiler, that class will be nested, directly or indirectly, in the class containing the function member, it will have private accessibility, and it will have a name reserved for compiler use ([§6.4.3](lexical-structure.md#643-identifiers)).
5629+
An enumerable object is typically an instance of a compiler-generated enumerable class that encapsulates the code in the iterator block and implements the enumerable interfaces, but other methods of implementation are possible. If an enumerable class is generated by the compiler, that class will be nested, directly or indirectly, in the class containing the function, it will have private accessibility, and it will have a name reserved for compiler use ([§6.4.3](lexical-structure.md#643-identifiers)).
56275630

56285631
An enumerable object may implement more interfaces than those specified above.
56295632

5630-
> *Note*: For example, an enumerable object may also implement `IEnumerator` and `IEnumerator<T>`, enabling it to serve as both an enumerable and an enumerator. Typically, such an implementation would return its own instance (to save allocations) from the first call to `GetEnumerator`. Subsequent invocations of `GetEnumerator`, if any, would return a new class instance, typically of the same class, so that calls to different enumerator instances will not affect each other. It cannot return the same instance even if the previous enumerator has already enumerated past the end of the sequence, since all future calls to an exhausted enumerator must throw exceptions. *end note*
5633+
> *Note*: For example, an enumerable object may also implement `IEnumerator` and `IEnumerator<T>`, enabling it to serve as both an enumerable and an enumerator. Typically, such an implementation would return its own instance (to save allocations) from the first call to `GetEnumerator`. Subsequent invocations of `GetEnumerator`, if any, would return a new class instance, typically of the same class, so that calls to different enumerator instances will not affect each other. *end note*
56315634
56325635
#### 15.15.6.2 The GetEnumerator or GetAsyncEnumerator method
56335636

56345637
An enumerable object provides an implementation of the `GetEnumerator` methods of the `IEnumerable` and `IEnumerable<T>` interfaces. The two `GetEnumerator` methods share a common implementation that acquires and returns an available enumerator object. The enumerator object is initialized with the argument values and instance value saved when the enumerable object was initialized, but otherwise the enumerator object functions as described in [§15.15.5](classes.md#15155-enumerator-objects).
56355638

5636-
An asynchronous enumerable object provides an implementation of the `GetAsyncEnumerator` method of the `IAsyncEnumerable<T>` interface. This method returns an available asynchronous enumerator object. The enumerator object is initialized with the argument values and instance value saved when the enumerable object was initialized, but otherwise the enumerator object functions as described in [§15.15.5](classes.md#15155-enumerator-objects).
5639+
An asynchronous enumerable object provides an implementation of the `GetAsyncEnumerator` method of the `IAsyncEnumerable<T>` interface. This method returns an available asynchronous enumerator object. The enumerator object is initialized with the argument values and instance value saved when the enumerable object was initialized, including the optional cancellation token, but otherwise the enumerator object functions as described in [§15.15.5](classes.md#15155-enumerator-objects). An asynchronous iterator method can mark one parameter as the cancellation token using `System.Runtime.CompilerServices.EnumeratorCancellationAttribute` (§enumerator-cancellation). An implementation shall provide a mechanism to combine cancellation tokens such that an asynchronous iterator is canceled when either cancellation token (the argument to `GetAsyncEnumerator` or the argument attributed with the attribute `System.Runtime.CompilerServices.EnumeratorCancellationAttribute`) requests cancellation.

0 commit comments

Comments
 (0)