Saturday, 16 November 2019

Iterating multiple times

There's something in the behaviour of IEnumerables-IEnumerators created by an Iterator method in C# that is quite confusing. Let's start by the behaviour of JavaScript generators, that is the expected one for me, and let's see then the oddities of the C# version.

In JavaScript, a generator function returns a generator object, an object that is both an iterable and an iterator. So when asking it for an iterator (via [Symbol.iterator]) it returns itself. Because of this, if we try to iterate multiple times our iterable, the iteration will only happen the first time. During this first iteration the iterable-iterator object reaches its end, so next iterations will do nothing.

function* getCountries(){
 yield "France";
 yield "Belgium";
 yield "Portugal";
}

let countriesGenOb = getcountries();

let countriesIterator1 = countriesGenOb[Symbol.iterator]();
console.log(countriesGenOb === countriesIterator1 ? "same object" : "different object"); //same object


console.log("- first iteration:");
for (let country of countriesGenOb){
 console.log(city);
}

//France
//Belgium
//Portugal

console.log("-------------");

console.log("- second iteration:");
//no iteration is done, countriesGenOb[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 country of countriesGenOb){
 console.log(city);
}

//Nothing gets printed

let countriesIterator2 = countriesGenOb[Symbol.iterator]();
let countriesIterator3 = countriesGenOb[Symbol.iterator]();
console.log(countriesGenOb === countriesIterator2 ? "Same reference" : "Different reference"); 
//same reference
console.log(countriesGenOb === countriesIterator3 ? "Same reference" : "Different reference"); 
//same reference

The behaviour in C# is different and surprising. An Iterator method returns an object that implements both IEnumerable and IEnumerator, but we can iterate it multiple times. This is like that because it seems that when we call GetEnumerator on and IEnumerable-IEnumerator that has already been enumerated, it does not return a reference to the same object, but to a new object implementing also IEnumerable and IEnumerator.

  var countries = GetCountries();
  Console.WriteLine(countries is IEnumerable); 
  //True
  Console.WriteLine(countries is IEnumerator); 
  //True

  Console.WriteLine(countries == countries.GetEnumerator() ? "Same reference" : "Different reference");
  //Same reference

  //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);
  
  //France
  //Belgium
  //Portugal
  
  Console.WriteLine("-----------------");
  Console.WriteLine(countries == countries.GetEnumerator() ? "Same reference" : "Different reference");
  //Different reference
  
  Console.WriteLine("- second iteration:");
  foreach(string country in countries)
   Console.WriteLine(country);
  
  //France
  //Belgium
  //Portugal

  Console.WriteLine("-----------------");

  var enumerator1 = countries.GetEnumerator();

  Console.WriteLine((enumerator1 == countries) ? "Same reference" : "Different reference"); 
  //Different reference
  

  var enumerator2 = countries.GetEnumerator();
  Console.WriteLine((enumerator1 == enumerator2) ? "Same reference" : "Different reference"); 
  //Different reference


<-- CSharp and JavaScript current sources F:\Main\MyWebCreatures\deploytonenyures\SourceCode\GeneratorsIterators_IterateMultipleTimes -->

No comments:

Post a Comment