Skip to content

Commit 66d766c

Browse files
Nigel-Ecmajskeet
authored andcommitted
Complete #1560 by adding deconstructing foreach
Closes #1602 - Complete #1560 by covering *foreach_statement* using a *deconstructor* - Add sample “Deconstructing foreach” - Change subclause title from “await foreach” to “Asynchronous foreach” for consistency with siblings - Also cleans up some whitespace
1 parent 854627a commit 66d766c

9 files changed

Lines changed: 3449 additions & 30 deletions

File tree

standard/statements.md

Lines changed: 112 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,22 +1106,31 @@ The `foreach` statement enumerates the elements of a collection, executing an em
11061106

11071107
```ANTLR
11081108
foreach_statement
1109-
: 'await'? 'foreach' '(' ref_kind? local_variable_type identifier
1110-
'in' expression ')' embedded_statement
1109+
: // synchronous foreach
1110+
'foreach' '(' ref_kind? local_variable_type identifier 'in' expression ')'
1111+
embedded_statement
1112+
| // asynchronous foreach
1113+
'await' 'foreach' '(' local_variable_type identifier 'in' expression ')'
1114+
embedded_statement
1115+
| // deconstructing foreach
1116+
'await'? 'foreach' '(' deconstructor 'in' expression ')'
1117+
embedded_statement
11111118
;
11121119
```
11131120

1114-
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.
1121+
There are three forms of the *foreach_statement*: *synchronous*, *asynchronous* and *deconstructing*; corresponding to the three alternatives of the above grammar.
1122+
1123+
The deconstructing foreach supports both synchronous and asynchronous forms and is described in §deconstructing-foreach.
11151124

1116-
It is a compile time error for both `await` and `ref_kind` to be present in a `foreach statement`.
1125+
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.
11171126

11181127
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.
11191128

11201129
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.
11211130

11221131
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.
11231132

1124-
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.
1133+
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.
11251134

11261135
1. Determine whether the type `X` of *expression* has an appropriate «GetEnumerator» method:
11271136
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.
@@ -1137,7 +1146,7 @@ The determination is similar for the synchronous and asynchronous versions. Diff
11371146

11381147
> *Note*: If *expression* has the value `null`, a `System.NullReferenceException` is thrown at run-time. *end note*
11391148
1140-
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).
1149+
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).
11411150

11421151
#### 13.9.5.2 Synchronous foreach
11431152

@@ -1166,7 +1175,7 @@ A `foreach` statement of the form
11661175
foreach (V v in x) «embedded_statement»
11671176
```
11681177

1169-
is then equivalent to:
1178+
is semantically equivalent to:
11701179

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

1197-
is then equivalent to:
1206+
is semantically equivalent to:
11981207

11991208
```csharp
12001209
{
@@ -1273,7 +1282,7 @@ The body of the `finally` block is constructed according to the following steps:
12731282
```
12741283
12751284
- Otherwise, the `finally` clause is expanded to:
1276-
1285+
12771286
```csharp
12781287
finally
12791288
{
@@ -1336,7 +1345,7 @@ The order in which `foreach` traverses the elements of an array, is as follows:
13361345
>
13371346
> *end example*
13381347
1339-
#### 13.9.5.3 await foreach
1348+
#### 13.9.5.3 Asynchronous foreach
13401349
13411350
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:
13421351
@@ -1356,19 +1365,21 @@ await foreach (T item in enumerable) «embedded_statement»
13561365
is semantically equivalent to:
13571366

13581367
```csharp
1359-
var enumerator = enumerable.GetAsyncEnumerator();
1360-
try
13611368
{
1362-
while (await enumerator.MoveNextAsync())
1369+
var enumerator = enumerable.GetAsyncEnumerator();
1370+
try
1371+
{
1372+
while (await enumerator.MoveNextAsync())
1373+
{
1374+
T item = enumerator.Current;
1375+
«embedded_statement»
1376+
}
1377+
}
1378+
finally
13631379
{
1364-
T item = enumerator.Current;
1365-
«embedded_statement»
1380+
// dispose of enumerator as described later in this clause.
13661381
}
13671382
}
1368-
finally
1369-
{
1370-
// dispose of enumerator as described later in this clause.
1371-
}
13721383
```
13731384

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

14121423
- Otherwise, the `finally` clause is expanded to:
1413-
1424+
14141425
```csharp
14151426
finally
14161427
{
@@ -1426,6 +1437,77 @@ The local variable `d` is not visible to or accessible to any user code. In par
14261437

14271438
> *Note*: An `await foreach` is not required to dispose of `e` synchronously if an asynchronous dispose mechanism is not available. *end note*
14281439
1440+
#### §deconstructing-foreach Deconstructing foreach
1441+
1442+
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*.
1443+
1444+
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*.
1445+
1446+
For a `foreach` statement of the form:
1447+
1448+
```csharp
1449+
foreachdeconstructor» in x) «embedded_statement»
1450+
```
1451+
1452+
is semantically equivalent to:
1453+
1454+
```csharp
1455+
{
1456+
E e = ((C)(x)).GetEnumerator();
1457+
try
1458+
{
1459+
while (e.MoveNext())
1460+
{
1461+
«deconstructor» = e.Current;
1462+
«embedded_statement»
1463+
}
1464+
}
1465+
finally
1466+
{
1467+
... // dispose of enumerator as for synchronous foreach
1468+
}
1469+
}
1470+
```
1471+
1472+
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:
1473+
1474+
- `C` and `E` are determined as for synchronous foreach
1475+
- `e` is not visible or accessible anywhere in the program accept as indicated in the above code
1476+
- the variables declared by the «deconstructor» are read-only to the «embedded_statement»
1477+
- the code in the `finally` block is determined as for synchronous foreach
1478+
1479+
An `await foreach` statement of the form:
1480+
1481+
```csharp
1482+
await foreachdeconstructor» in enumerable) «embedded_statement»
1483+
```
1484+
1485+
is semantically equivalent to:
1486+
1487+
```csharp
1488+
{
1489+
var enumerator = enumerable.GetAsyncEnumerator();
1490+
try
1491+
{
1492+
while (await enumerator.MoveNextAsync())
1493+
{
1494+
«deconstructor» = enumerator.Current;
1495+
«embedded_statement»
1496+
}
1497+
}
1498+
finally
1499+
{
1500+
// dispose of enumerator as for asynchronous foreach
1501+
}
1502+
}
1503+
```
1504+
1505+
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:
1506+
1507+
- `enumerator` is not visible or accessible anywhere in the program accept as indicated in the above code
1508+
- the variables declared by the «deconstructor» are read-only to the «embedded_statement»
1509+
- the code in the `finally` block is determined as for asynchronous foreach
1510+
14291511
## 13.10 Jump statements
14301512

14311513
### 13.10.1 General
@@ -1813,14 +1895,14 @@ If an exception is thrown during execution of a `finally` block, and is not caug
18131895
> {
18141896
> Console.WriteLine("Catch");
18151897
> }
1816-
>
1898+
>
18171899
> bool ExceptionFilter(Exception ex)
18181900
> {
18191901
> Console.WriteLine("Filter");
18201902
> return true;
18211903
> }
18221904
> }
1823-
>
1905+
>
18241906
> static void Method()
18251907
> {
18261908
> try
@@ -1945,10 +2027,10 @@ A `using` statement is translated into three parts: acquisition, usage, and disp
19452027
A `using` statement of the form
19462028
19472029
```csharp
1948-
using (ResourceType resource = «expression» ) «statement»
2030+
using (ResourceType resource = «expression») «statement»
19492031
```
19502032
1951-
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
2033+
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:
19522034
19532035
```csharp
19542036
{
@@ -2080,16 +2162,16 @@ using (ResourceType rN = eN)
20802162
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
20812163
20822164
```csharp
2083-
await using (ResourceType resource = «expression» ) «statement»
2165+
await using (ResourceType resource = «expression») «statement»
20842166
```
20852167
20862168
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:
20872169
20882170
```csharp
2089-
await using (ResourceType resource = «expression» ) «statement»
2171+
await using (ResourceType resource = «expression») «statement»
20902172
```
20912173
2092-
is semantically equivalent to
2174+
is semantically equivalent to:
20932175
20942176
```csharp
20952177
{
@@ -2128,7 +2210,7 @@ using «local_variable_type» «local_variable_declarators»
21282210
// statements
21292211
```
21302212

2131-
is semantically equivalent to
2213+
is semantically equivalent to:
21322214

21332215
```csharp
21342216
usinglocal_variable_type» «local_variable_declarators»)
@@ -2144,7 +2226,7 @@ await using «local_variable_type» «local_variable_declarators»
21442226
// statements
21452227
```
21462228

2147-
is semantically equivalent to
2229+
is semantically equivalent to:
21482230

21492231
```csharp
21502232
await usinglocal_variable_type» «local_variable_declarators»)
@@ -2163,7 +2245,7 @@ static void M()
21632245
using FileStream f2 = new FileStream(...), f3 = new FileStream(...);
21642246
...
21652247
// Dispose f3
2166-
// Dispose f2
2248+
// Dispose f2
21672249
// Dispose f1
21682250
}
21692251
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Sample: Deconstructing foreach
2+
3+
Samples of deconstructing foreach statements

0 commit comments

Comments
 (0)