diff --git a/standard/statements.md b/standard/statements.md index 083d0b7cc..52e9d58a8 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -1364,80 +1364,42 @@ An `await foreach` statement of the form await foreach (T item in enumerable) «embedded_statement» ``` -is semantically equivalent to: +uses an `await using` statement ([§13.14.1](statements.md#13141-general)) in its expansion if either of the following conditions holds: + +- The member lookup and overload resolution for `DisposeAsync` specified for an `await using` statement, using the enumerator type `E` as `ResourceType`, select an accessible instance method. +- There is an implicit conversion from `E` to `System.IAsyncDisposable`. + +In this case, the statement is semantically equivalent to: ```csharp +await using (E enumerator = enumerable.GetAsyncEnumerator()) { - var enumerator = enumerable.GetAsyncEnumerator(); - try + while (await enumerator.MoveNextAsync()) { - while (await enumerator.MoveNextAsync()) - { - T item = enumerator.Current; - «embedded_statement» - } - } - finally - { - // dispose of enumerator as described later in this clause. + T item = enumerator.Current; + «embedded_statement» } } ``` -In the case where the expression `enumerable` represents a method call expression and one of the parameters is marked with the `EnumeratorCancellationAttribute` ([§23.5.8](attributes.md#2358-the-enumeratorcancellation-attribute)) the `CancellationToken` is passed to the `GetAsyncEnumerator` method. Other library methods may require a `CancellationToken` is passed to `GetAsyncEnumerator`. When those methods are part of the expression `enumerable`, the tokens shall be combined into a single token as if by `CreateLinkedTokenSource` and its `Token` property. - -The body of the `finally` block is constructed according to the following steps: - -- If `E` has an accessible `DisposeAsync()` method where the return type is awaitable ([§12.9.9.2](expressions.md#12992-awaitable-expressions)), the `finally` clause is expanded to the semantic equivalent of: +> *Note*: The return type of the selected method is not required to be awaitable in order to choose this expansion. If it is not awaitable, the `await using` statement produces a compile-time error, even if an implicit conversion to `System.IAsyncDisposable` exists. *end note* - ```csharp - finally - { - await e.DisposeAsync(); - } - ``` - -- Otherwise, if there is an implicit conversion from `E` to the `System.IAsyncDisposable` interface and `E` is a non-nullable value type then the `finally` clause is expanded to the semantic equivalent of: - - ```csharp - finally - { - await ((System.IAsyncDisposable)e).DisposeAsync(); - } - ``` +If neither condition holds, the statement is semantically equivalent to: - except that if `E` is a value type, or a type parameter instantiated to a value type, then the conversion of `e` to `System.IAsyncDisposable` shall not cause boxing to occur. -- Otherwise, if `E` is a `ref struct` type and has an accessible `Dispose()` method, the `finally` clause is expanded to the semantic equivalent of: - - ```csharp - finally +```csharp +{ + E enumerator = enumerable.GetAsyncEnumerator(); + while (await enumerator.MoveNextAsync()) { - e.Dispose(); + T item = enumerator.Current; + «embedded_statement» } - ``` - -- Otherwise, if `E` is a sealed type, the `finally` clause is expanded to an empty block: - - ```csharp - finally {} - ``` - -- Otherwise, the `finally` clause is expanded to: - - ```csharp - finally - { - System.IAsyncDisposable d = e as System.IAsyncDisposable; - if (d != null) - { - await d.DisposeAsync(); - } - } - ``` +} +``` -The local variable `d` is not visible to or accessible to any user code. In particular, it does not conflict with any other variable whose scope includes the `finally` block. +In the case where the expression `enumerable` represents a method call expression and one of the parameters is marked with the `EnumeratorCancellationAttribute` ([§23.5.8](attributes.md#2358-the-enumeratorcancellation-attribute)) the `CancellationToken` is passed to the `GetAsyncEnumerator` method. Other library methods may require a `CancellationToken` is passed to `GetAsyncEnumerator`. When those methods are part of the expression `enumerable`, the tokens shall be combined into a single token as if by `CreateLinkedTokenSource` and its `Token` property. -> *Note*: An `await foreach` is not required to dispose of `e` synchronously if an asynchronous dispose mechanism is not available. *end note* +> *Note*: An `await foreach` is not required to dispose of `enumerator` synchronously if an asynchronous dispose mechanism is not available. *end note* #### 13.9.5.4 Deconstructing foreach @@ -1486,31 +1448,11 @@ An `await foreach` statement of the form: await foreach («deconstructor» in enumerable) «embedded_statement» ``` -is semantically equivalent to: +uses the same expansions as asynchronous foreach ([§13.9.5.3](statements.md#13953-asynchronous-foreach)), replacing `T item = enumerator.Current;` with `«deconstructor» = enumerator.Current;`. This *deconstructing_assignment* contains the iteration-variable declarations in its *deconstructor*: each *declaration_expression* other than a discard declares one iteration variable, and each discard ([§9.2.9.2](variables.md#9292-discards)) declares none: -```csharp -{ - var enumerator = enumerable.GetAsyncEnumerator(); - try - { - while (await enumerator.MoveNextAsync()) - { - «deconstructor» = enumerator.Current; - «embedded_statement» - } - } - finally - { - // dispose of enumerator as for asynchronous foreach - } -} -``` - -This follows the behavior of asynchronous foreach ([§13.9.5.3](statements.md#13953-asynchronous-foreach)), differing by replacing the declaration and initialisation of a single iteration variable with a *deconstructing_assignment* in which the *deconstructor* contains the iteration-variable declarations: each *declaration_expression* other than a discard declares one iteration variable, and each discard ([§9.2.9.2](variables.md#9292-discards)) declares none: - -- `enumerator` is not visible or accessible anywhere in the program except as indicated in the above code +- `enumerator` is not visible or accessible anywhere in the program except as indicated in those expansions - the variables declared within the «deconstructor» are read-only to the «embedded_statement» -- the code in the `finally` block is determined as for asynchronous foreach +- the conditions for using the `await using` expansion are the same as for asynchronous foreach > *Example*: A deconstructing foreach uses discards as placeholders for elements that are not needed. Here each element of the collection is a tuple whose second element is discarded: > @@ -2033,9 +1975,16 @@ non_ref_local_variable_declaration ; ``` -A ***resource type*** is either a class or non-ref struct that implements either or both of the `System.IDisposable` or `System.IAsyncDisposable` interfaces, which includes a single parameterless method named `Dispose` and/or `DisposeAsync`; or a ref struct that includes a method named `Dispose` having the same signature as that declared by `System.IDisposable`. Code that is using a resource can call `Dispose` or `DisposeAsync` to indicate that the resource is no longer needed. +A ***resource type*** is one of the following: + +- A class or non-ref struct that implements the `System.IDisposable` interface, which includes a single parameterless method named `Dispose`. +- A ref struct that includes a method named `Dispose` having the same signature as that declared by `System.IDisposable`. +- A class or non-ref struct that has an accessible instance `DisposeAsync()` method with an awaitable return type ([§12.9.9.2](expressions.md#12992-awaitable-expressions)). +- A class or non-ref struct that implements the `System.IAsyncDisposable` interface, which includes a single parameterless method named `DisposeAsync`. -If the form of *resource_acquisition* is *non_ref_local_variable_declaration* then the type of the *non_ref_local_variable_declaration* shall be either `dynamic` or a resource type. If the form of *resource_acquisition* is *expression* then this expression shall have a resource type. If `await` is present, the resource type shall implement `System.IAsyncDisposable`. A `ref struct` type cannot be the resource type for a `using` statement with the `await` modifier. +Code that is using a resource can call `Dispose` or `DisposeAsync` to indicate that the resource is no longer needed. + +If the form of *resource_acquisition* is *non_ref_local_variable_declaration* then the type of the *non_ref_local_variable_declaration* shall be either `dynamic` or a resource type. If the form of *resource_acquisition* is *expression* then this expression shall have a resource type. If `await` is present, the resource type shall have an accessible instance `DisposeAsync()` method with an awaitable return type or implement `System.IAsyncDisposable`. A `ref struct` type cannot be the resource type for a `using` statement with the `await` modifier. Local variables declared in a *resource_acquisition* are read-only, and shall include an initializer. A compile-time error occurs if the embedded statement attempts to modify these local variables (via assignment or the `++` and `--` operators), take the address of them, or pass them as reference or output parameters. @@ -2096,15 +2045,18 @@ Otherwise, the formulation is } finally { - IDisposable d = (IDisposable)resource; - if (d != null) + if ((object)resource != null) { - d.Dispose(); + ((IDisposable)resource).Dispose(); } } } ``` +except that the cast of `resource` to `System.IDisposable` shall not cause boxing to occur. + +> *Note*: When `ResourceType` is a non-nullable value type, or a type parameter instantiated to a non-nullable value type, the null check shown above may be elided. *end note* + For ref struct resources, the only semantically equivalent formulation is ```csharp @@ -2176,19 +2128,44 @@ using (ResourceType rN = eN) > > *end example* -When `ResourceType` is a reference type that implements `IAsyncDisposable`. Other formulations for `await using` perform similar substitutions from the synchronous `Dispose` method to the asynchronous `DisposeAsync` method. An `await using` statement of the form +An `await using` statement of the form ```csharp await using (ResourceType resource = «expression») «statement» ``` -is semantically equivalent to the formulations shown below with `IAsyncDisposable` instead of `IDisposable`, `DisposeAsync` instead of `Dispose`, and the `Task` returned from `DisposeAsync` is `await`ed: +first performs member lookup ([§12.5](expressions.md#125-member-lookup)) on `ResourceType` with the identifier `DisposeAsync` and no type arguments. If the result is a method group and overload resolution ([§12.6.4](expressions.md#1264-overload-resolution)) with an empty argument list selects an accessible instance method, that method is selected for asynchronous disposal. If its return type is not awaitable ([§12.9.9.2](expressions.md#12992-awaitable-expressions)), an error is produced and no further steps are taken. + +When such a method is selected, the statement is semantically equivalent to: ```csharp -await using (ResourceType resource = «expression») «statement» +{ + ResourceType resource = «expression»; + try + { + «statement»; + } + finally + { + if ((object)resource != null) + { + await resource.DisposeAsync(); + } + } +} ``` -is semantically equivalent to: +> *Note*: If `ResourceType` is a nullable value type ([§8.3.12](types.md#8312-nullable-value-types)), member lookup for `DisposeAsync` is performed on `ResourceType`, not on its underlying type. *end note* + + + +> *Note*: When `ResourceType` is a non-nullable value type, or a type parameter instantiated to a non-nullable value type, the null check shown above may be elided. *end note* + +If no such method is selected, the corresponding synchronous formulations apply with `IAsyncDisposable` instead of `IDisposable`, `DisposeAsync` instead of `Dispose`, and the `ValueTask` returned from `DisposeAsync` awaited. The formulation for ref struct resources does not apply, since a ref struct cannot be the resource type of an `await using` statement. + +If no method is selected and there is no implicit conversion to `System.IAsyncDisposable`, a compile-time error occurs. + +For example, when `ResourceType` is a reference type that implements `IAsyncDisposable`, the statement is semantically equivalent to: ```csharp { @@ -2199,15 +2176,18 @@ is semantically equivalent to: } finally { - IAsyncDisposable d = (IAsyncDisposable)resource; - if (d != null) + if ((object)resource != null) { - await d.DisposeAsync(); + await ((IAsyncDisposable)resource).DisposeAsync(); } } } ``` +These disposal rules are also used by asynchronous foreach ([§13.9.5.3](statements.md#13953-asynchronous-foreach)). + +The expression form of `await using` has the same possible formulations, with `resource` being a temporary variable inaccessible to user code. + > *Note*: Any jump statements ([§13.10](statements.md#1310-jump-statements)) in the *embedded_statement* must conform to expanded form of the `using` statement. *end note* ### 13.14.2 Using declaration