I’ve noticed a fascinating difference between JavaScript generator functions and C# iterator methods, despite both implementing similar concepts with different terminology (Microsoft’s choice of “enumerable/enumerator/iterator” over the more common “iterable/iterator/generator” is debatable). Both languages use these special functions/methods with a yield statement, transforming them into state machines.
In JavaScript, a generator function produces a generator object that acts as both iterable and iterator. Requesting an iterator from this object using [Symbol.iterator]
returns the object itself. Consequently, iterating multiple times over the same generator object only works on the first attempt. Subsequent iterations yield no results because the iterator is already positioned at the end.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| function* getCities(){
yield "Toulouse";
yield "Xixon";
yield "Berlin";
}
let citiesGenOb = getCities();
let citiesIterator1 = citiesGenOb[Symbol.iterator]();
console.log(citiesGenOb === citiesIterator1 ? "same object" : "different object"); //same object
console.log("- first iteration:");
for (let city of citiesGenOb){
console.log(city);
}
console.log("-------------");
console.log("- second iteration:");
//no iteration is done, citiesGenOb[Symbol.iterator] is returning the same object
//that was already iterated to the end in the previous loop
//very interesting, this behaviour is different from C#, here the generator object (this is both iterable and iterator) is returning itself, rather than a copy
for (let city of citiesGenOb){
console.log(city);
}
let citiesIterator2 = citiesGenOb[Symbol.iterator]();
console.log(citiesGenOb === citiesIterator2 ? "same object" : "different object"); //same object
/*
same object
- first iteration:
Toulouse
Xixon
Berlin
-------------
- second iteration:
same object
*/
|
The generator and its iterator (implicitly obtained via citiesGenOb[Symbol.iterator]
in the “for…of” loop) are inseparable. Once the first iteration is complete, attempting to iterate again yields nothing because the iterator remains at the end.
Conversely, in C#, an iterator method returns an object of a compiler-generated class implementing both IEnumerable and IEnumerator interfaces. One might anticipate the GetEnumerator
method of this object to return the same instance based on JavaScript’s behavior. However, the C# compiler behaves differently:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| private static IEnumerable GetCountries()
{
yield return "France";
yield return "Belgium";
yield return "Portugal";
}
IEnumerable countries = GetCountries();
var enumerator1 = countries.GetEnumerator();
Console.WriteLine((enumerator1 == countries) ? "Same reference" : "Different reference"); //Different
//the Iterator method is returning an IEnumerable/IEnumerator object, but the thing is that calling to GetEnumerator returns a new instance, rather than the object itself
//Because of that the 2 loops do a whole iteration, and enumerator1 and 2 are different objects.
Console.WriteLine("- first iteration:");
foreach(string country in countries)
Console.WriteLine(country);
Console.WriteLine("- second iteration:");
foreach(string country in countries)
Console.WriteLine(country);
enumerator1 = countries.GetEnumerator();
Console.WriteLine((enumerator1 == countries) ? "Same reference" : "Different reference"); //Different
var enumerator2 = countries.GetEnumerator();
Console.WriteLine((enumerator1 == enumerator2) ? "Same reference" : "Different reference");
/*
Same reference
- first iteration:
France
Belgium
Portugal
- second iteration:
France
Belgium
Portugal
Different reference
Different reference
*/
|
Interestingly, calling GetEnumerator
on the IEnumerable/IEnumerator object returns the same instance only if it hasn’t been iterated yet (as seen in the first “Same reference” output). Subsequent calls create new instances, enabling multiple iterations over the same IEnumerable/IEnumerator object (both “first” and “second” iterations complete successfully).
Decompiling the compiler-generated IEnumerable/IEnumerator class reveals this behavior:
[Image of decompiled code](
)
Therefore, in JavaScript, if a function requires an iterable for multiple iterations, it’s crucial to pass the generator function itself instead of the generator object. This allows invoking it repeatedly for fresh iterations.
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3nYngHBv-d7WTsQErYvC_7M2RnfhhFYcvy2tX-f-enXDOahZWN54e8mVMnbeqSr4DeK24dmk2g7xPRUtCcomqZuf7SmdZ5e5aCRVdAm4EFlalFyxSXhyQkYBPchxyCumCZnpyfZWOSooK/s1600/2017-11-22_171446.jpg
