Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 75 additions & 95 deletions standard/statements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
>
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Editorial note: This repeats the text in line 1391 (asynchronous foreach). If possible, it'd be nice to de-duplicate it, and provide an xref. If de-dup is readable, at least an xref (both ways) should be added so we keep them in sync in the future.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried a dedup! Commit 0e1abef. What do you think?


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*
Comment thread
jnm2 marked this conversation as resolved.
<!-- markdownlint-disable MD028 -->

<!-- markdownlint-enable MD028 -->
> *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
{
Expand All @@ -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
Expand Down
Loading