Skip to content
Merged
Show file tree
Hide file tree
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
142 changes: 112 additions & 30 deletions standard/statements.md
Original file line number Diff line number Diff line change
Expand Up @@ -1106,22 +1106,31 @@ The `foreach` statement enumerates the elements of a collection, executing an em

```ANTLR
foreach_statement
: 'await'? 'foreach' '(' ref_kind? local_variable_type identifier
'in' expression ')' embedded_statement
: // synchronous foreach
'foreach' '(' ref_kind? local_variable_type identifier 'in' expression ')'
embedded_statement
| // asynchronous foreach
'await' 'foreach' '(' local_variable_type identifier 'in' expression ')'
embedded_statement
| // deconstructing foreach
'await'? 'foreach' '(' deconstructor 'in' expression ')'
embedded_statement
;
```

The *local_variable_type* and *identifier* of a foreach statement declare the ***iteration variable*** of the statement. If the `var` identifier is given as the *local_variable_type*, and no type named `var` is in scope, the iteration variable is said to be an ***implicitly typed iteration variable***, and its type is taken to be the element type of the `foreach` statement, as specified below.
There are three forms of the *foreach_statement*: *synchronous*, *asynchronous* and *deconstructing*; corresponding to the three alternatives of the above grammar.

The deconstructing foreach supports both synchronous and asynchronous forms and is described in §deconstructing-foreach.

It is a compile time error for both `await` and `ref_kind` to be present in a `foreach statement`.
The *local_variable_type* and *identifier* of a foreach statement declare the ***iteration variable*** of the statement. If the `var` identifier is given as the *local_variable_type*, and no type named `var` is in scope, the iteration variable is said to be an ***implicitly typed iteration variable***, and its type is taken to be the element type of the `foreach` statement, as specified below.

If the *foreach_statement* contains both or neither `ref` and `readonly`, the iteration variable denotes a variable that is treated as read-only. Otherwise, if *foreach_statement* contains `ref` without `readonly`, the iteration variable denotes a variable that shall be writable.

The iteration variable corresponds to a local variable with a scope that extends over the embedded statement. During execution of a `foreach` statement, the iteration variable represents the collection element for which an iteration is currently being performed. If the iteration variable denotes a read-only variable, a compile-time error occurs if the embedded statement attempts to modify it (via assignment or the `++` and `--` operators) or pass it as a reference or output parameter.

The compile-time processing of a `foreach` statement first determines the ***collection type*** (`C`), ***enumerator type*** (`E`) and ***iteration type*** (`T`, `ref T` or `ref readonly T`) of the expression.

The determination is similar for the synchronous and asynchronous versions. Different interfaces with different methods and return types distinguish the synchronous and asynchronous versions. The general process proceeds as follows. Names within ‘«’ and ‘»’ are placeholders for the actual names for synchronous and asynchronous iterators. The types allowed for «GetEnumerator», «MoveNext», «IEnumerable»\<T>, «IEnumerator»\<T>, and any other distinctions are detailed in [§13.9.5.2](statements.md#13952-synchronous-foreach) for a synchronous `foreach` statement, and in [§13.9.5.3](statements.md#13953-await-foreach) for an asynchronous `foreach` statement.
The determination is similar for the synchronous and asynchronous versions. Different interfaces with different methods and return types distinguish the synchronous and asynchronous versions. The general process proceeds as follows. Names within ‘«’ and ‘»’ are placeholders for the actual names for synchronous and asynchronous iterators. The types allowed for «GetEnumerator», «MoveNext», «IEnumerable»\<T>, «IEnumerator»\<T>, and any other distinctions are detailed in [§13.9.5.2](statements.md#13952-synchronous-foreach) for a synchronous `foreach` statement, and in [§13.9.5.3](statements.md#13953-asynchronous-foreach) for an asynchronous `foreach` statement.

1. Determine whether the type `X` of *expression* has an appropriate «GetEnumerator» method:
1. Perform member lookup on the type `X` with identifier «GetEnumerator» and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described in step 2. It is recommended that a warning be issued if member lookup produces anything except a method group or no match.
Expand All @@ -1137,7 +1146,7 @@ The determination is similar for the synchronous and asynchronous versions. Diff

> *Note*: If *expression* has the value `null`, a `System.NullReferenceException` is thrown at run-time. *end note*

An implementation is permitted to implement a given *foreach_statement* differently; e.g., for performance reasons, as long as the behavior is consistent with the expansions described in [§13.9.5.2](statements.md#13952-synchronous-foreach) and [§13.9.5.3](statements.md#13953-await-foreach).
An implementation is permitted to implement a given *foreach_statement* differently; e.g., for performance reasons, as long as the behavior is consistent with the expansions described in [§13.9.5.2](statements.md#13952-synchronous-foreach) and [§13.9.5.3](statements.md#13953-asynchronous-foreach).

#### 13.9.5.2 Synchronous foreach

Expand Down Expand Up @@ -1166,7 +1175,7 @@ A `foreach` statement of the form
foreach (V v in x) «embedded_statement»
```

is then equivalent to:
is semantically equivalent to:

```csharp
{
Expand Down Expand Up @@ -1194,7 +1203,7 @@ When the iteration variable is a reference variable ([§9.7](variables.md#97-ref
foreach (ref V v in x) «embedded_statement»
```

is then equivalent to:
is semantically equivalent to:

```csharp
{
Expand Down Expand Up @@ -1273,7 +1282,7 @@ The body of the `finally` block is constructed according to the following steps:
```

- Otherwise, the `finally` clause is expanded to:

```csharp
finally
{
Expand Down Expand Up @@ -1336,7 +1345,7 @@ The order in which `foreach` traverses the elements of an array, is as follows:
>
> *end example*

#### 13.9.5.3 await foreach
#### 13.9.5.3 Asynchronous foreach

An asynchronous foreach uses the `await foreach` syntax. The determination of ***collection type***, ***enumeration type*** and ***iteration type*** proceeds as described in [§13.9.5.1](statements.md#13951-general), where:

Expand All @@ -1356,19 +1365,21 @@ await foreach (T item in enumerable) «embedded_statement»
is semantically equivalent to:

```csharp
var enumerator = enumerable.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
var enumerator = enumerable.GetAsyncEnumerator();

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.

It looks like the only change here is indenting. Correct?

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.

The expansions for the others where all wrapped in an enclosing block and this one was missed/different. So yes, the whole thing was indented and unindented { & } added to wrap it – not that the diff makes that easy to spot!

try
{
while (await enumerator.MoveNextAsync())
{
T item = enumerator.Current;
«embedded_statement»
}
}
finally
{
T item = enumerator.Current;
«embedded_statement»
// dispose of enumerator as described later in this clause.
}
}
finally
{
// dispose of enumerator as described later in this clause.
}
```

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.
Expand Down Expand Up @@ -1410,7 +1421,7 @@ The body of the `finally` block is constructed according to the following steps:
```

- Otherwise, the `finally` clause is expanded to:

```csharp
finally
{
Expand All @@ -1426,6 +1437,77 @@ The local variable `d` is not visible to or accessible to any user code. In par

> *Note*: An `await foreach` is not required to dispose of `e` synchronously if an asynchronous dispose mechanism is not available. *end note*

#### §deconstructing-foreach Deconstructing foreach

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.

I ran a couple tests, and we need to specify discards in a deconstructing foreach. I think the language should be similar to what was added in #1639.

What's interesting here is that a synchronous and asynchronous foreach don't allow the iteration variable to be a discard. However, in a deconstructing foreach, any number of the iteration variables may be a discard (from 0 to all).

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.

@BillWagner – see reply on line 1442

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.

@BillWagner PS: On your “What’s interesting” – are you using “interesting” as (apocryphally?) used in a Chinese curse? ;-) BTW a certain compiler rejects ... is _ but accepts ... is (_) – can that be justified? Maybe. Is it confusing? Probably. We “live in interesting times”.

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.

@Nigel-Ecma This comment helped me understand the final changes needed for #1688. ... is _ is a discard_pattern, but ... is (_) is a parenthesised_pattern that encloses a discard_pattern. Because the discard_pattern is a subpattern, it's allowed.


A deconstructing foreach replaces the declaration and initialisation of a single iteration variable per iteration, in the synchronous and asynchronous foreach statements, with a collection of zero or more iteration variables declared per iteration within the *deconstructor*([§12.23.3](expressions.md#12233-deconstructing-assignment)) of a *deconstructing_assignment*.

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.

Similar to the changes in 1560, this should be "2 or more":

Suggested change
A deconstructing foreach replaces the declaration and initialisation of a single iteration variable per iteration, in the synchronous and asynchronous foreach statements, with a collection of zero or more iteration variables declared per iteration within the *deconstructor*([§12.23.3](expressions.md#12233-deconstructing-assignment)) of a *deconstructing_assignment*.
A deconstructing foreach replaces the declaration and initialisation of a single iteration variable per iteration, in the synchronous and asynchronous foreach statements, with a collection of two or more iteration variables declared per iteration within the *deconstructor*([§12.23.3](expressions.md#12233-deconstructing-assignment)) of a *deconstructing_assignment*.

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.

@BillWagner – I had completely forgotten that #1639 introduced targets and counted them rather than assigned named variables, so I just wrote this doing the same as that is what is important – discards are discarded afterall :-)

Your proposed change here however makes it “two or more iteration variables” so more wordsmithing would be required to go the “target” route. I think targets would not fit well here, but then I went with the consensus on §12.23 so that is not too surprising. [Note that #1639 actually contradicts itself, it starts by stating two or more targets are assigned, then later that discard targets are not assigned (“but no value is stored”).]

That discards may be present is covered just below in line 1444. I did consider whether to list the allowed, which would have surfaced discards, or disallowed alternatives, and decided the key issue was that existing variables could not be assigned to so highlighted that by just listed the disallowed variable_reference.

Suggestion: We leave this PR as is on this topic and then you & @jskeet raise an issue to align the text in §12.23 to either use targets (which are not all assigned) in both, neither, or go with variety is the spice of life :-) Mark it pre-C#9 so its not a blocker – what exists here & in §12.23 are not wrong per se.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That suggestion is fine by me.


All variables assigned to by the *deconstructor* must be declared within the *deconstructor*, it is a compile time error for any *deconstructor_element* to be a *variable_reference*.

A foreach statement of the form:

```csharp
foreach («deconstructor» in x) «embedded_statement»
```

is semantically equivalent to:

```csharp
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
«deconstructor» = e.Current;
«embedded_statement»
}
}
finally
{
... // dispose of enumerator as for synchronous foreach
}
}
```

This follows the behavior of synchronous foreach ([§13.9.5.2](statements.md#13952-synchronous-foreach)), differing by replacing the delaration and initialisation of a single iteration variable with a *deconstructing_assignment* which declares and assigns zero or more initialisation variables:

- `C` and `E` are determined as for synchronous foreach
- `e` is not visible or accessible anywhere in the program accept as indicated in the above code
- the variables declared by the «deconstructor» are read-only to the «embedded_statement»
- the code in the `finally` block is determined as for synchronous foreach

An `await foreach` statement of the form:

```csharp
await foreach («deconstructor» in enumerable) «embedded_statement»
```

is semantically equivalent to:

```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 delaration and initialisation of a single iteration variable with a *deconstructing_assignment* which declares and assigns zero or more initialisation variables:

- `enumerator` is not visible or accessible anywhere in the program accept as indicated in the above code
- the variables declared by the «deconstructor» are read-only to the «embedded_statement»
- the code in the `finally` block is determined as for asynchronous foreach

## 13.10 Jump statements

### 13.10.1 General
Expand Down Expand Up @@ -1813,14 +1895,14 @@ If an exception is thrown during execution of a `finally` block, and is not caug
> {
> Console.WriteLine("Catch");
> }
>
>
> bool ExceptionFilter(Exception ex)
> {
> Console.WriteLine("Filter");
> return true;
> }
> }
>
>
> static void Method()
> {
> try
Expand Down Expand Up @@ -1945,10 +2027,10 @@ A `using` statement is translated into three parts: acquisition, usage, and disp
A `using` statement of the form

```csharp
using (ResourceType resource = «expression» ) «statement»
using (ResourceType resource = «expression») «statement»
```

corresponds to one of three possible formulations. For class and non-ref struct resources, when `ResourceType` is a non-nullable value type or a type parameter with the value type constraint ([§15.2.5](classes.md#1525-type-parameter-constraints)), the formulation is semantically equivalent to
corresponds to one of three possible formulations. For class and non-ref struct resources, when `ResourceType` is a non-nullable value type or a type parameter with the value type constraint ([§15.2.5](classes.md#1525-type-parameter-constraints)), the formulation is semantically equivalent to:

```csharp
{
Expand Down Expand Up @@ -2080,16 +2162,16 @@ using (ResourceType rN = eN)
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

```csharp
await using (ResourceType resource = «expression» ) «statement»
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:

```csharp
await using (ResourceType resource = «expression» ) «statement»
await using (ResourceType resource = «expression») «statement»
```

is semantically equivalent to
is semantically equivalent to:

```csharp
{
Expand Down Expand Up @@ -2128,7 +2210,7 @@ using «local_variable_type» «local_variable_declarators»
// statements
```

is semantically equivalent to
is semantically equivalent to:

```csharp
using («local_variable_type» «local_variable_declarators»)
Expand All @@ -2144,7 +2226,7 @@ await using «local_variable_type» «local_variable_declarators»
// statements
```

is semantically equivalent to
is semantically equivalent to:

```csharp
await using («local_variable_type» «local_variable_declarators»)
Expand All @@ -2163,7 +2245,7 @@ static void M()
using FileStream f2 = new FileStream(...), f3 = new FileStream(...);
...
// Dispose f3
// Dispose f2
// Dispose f2
// Dispose f1
}
```
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Sample: Deconstructing foreach

Samples of deconstructing foreach statements
Loading
Loading