-
Notifications
You must be signed in to change notification settings - Fork 97
Complete #1560 by adding deconstructing foreach #1689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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. | ||||||
|
|
@@ -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 | ||||||
|
|
||||||
|
|
@@ -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 | ||||||
| { | ||||||
|
|
@@ -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 | ||||||
| { | ||||||
|
|
@@ -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 | ||||||
| { | ||||||
|
|
@@ -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: | ||||||
|
|
||||||
|
|
@@ -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(); | ||||||
| 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. | ||||||
|
|
@@ -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 | ||||||
| { | ||||||
|
|
@@ -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 | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @BillWagner – see reply on line 1442
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||
|
|
||||||
| 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*. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
|
|
@@ -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 | ||||||
|
|
@@ -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 | ||||||
| { | ||||||
|
|
@@ -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 | ||||||
| { | ||||||
|
|
@@ -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») | ||||||
|
|
@@ -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») | ||||||
|
|
@@ -2163,7 +2245,7 @@ static void M() | |||||
| using FileStream f2 = new FileStream(...), f3 = new FileStream(...); | ||||||
| ... | ||||||
| // Dispose f3 | ||||||
| // Dispose f2 | ||||||
| // Dispose f2 | ||||||
| // Dispose f1 | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # Sample: Deconstructing foreach | ||
|
|
||
| Samples of deconstructing foreach statements |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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!