MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Iteratoare și operatorul yield

Un iterativ reprezintă un bloc de cod care utilizează operatorul yield pentru a parcurge un set de valori. Acest bloc de cod poate reprezenta corpul unei metode, al unui operator sau blocul get al unei proprietăți.

Un iterativ utilizează două instrucțiuni speciale:

  • yield return: definește elementul returnat
  • yield break: indică faptul că secvența nu mai are elemente

Să vedem un mic exemplu:

Numbers numbers = new Numbers();
foreach (int n in numbers)
{
   Console.WriteLine(n);
}

class Numbers
{
   public IEnumerator<int> GetEnumerator()
   {
       for (int i = 0; i < 6; i++)
       {
           yield return i * i;
       }
   }
}

În clasa Numbers, metoda GetEnumerator() reprezintă de fapt un iterativ. Cu ajutorul operatorului yield return se returnează o valoare (în acest caz, pătratul numărului).

În program, cu ajutorul ciclului foreach, putem parcurge obiectul Numbers ca o colecție obișnuită. La obținerea fiecărui element în ciclul foreach, va fi declanșat operatorul yield return, care va returna un element și va memora poziția curentă.

Datorită iteratoarelor, putem merge mai departe și implementa cu ușurință parcurgerea unui număr în ciclul foreach:

foreach(var n in 5) Console.WriteLine(n);
foreach (var n in -5) Console.WriteLine(n);

static class Int32Extension
{
   public static IEnumerator<int> GetEnumerator(this int number)
   {
       int k = (number > 0) ? number : 0;
       for (int i = number - k; i <= k; i++) yield return i;
   }
}

În acest caz, iterativul este implementat ca o metodă de extensie pentru tipul int sau System.Int32. În metoda iterativului, returnăm de fapt toate valorile întregi de la 0 la numărul curent. Rezultatul în consolă:

0
1
2
3
4
5
-5
-4
-3
-2
-1
0

Un alt exemplu: să presupunem că avem o colecție Company, care reprezintă o companie și care stochează într-un array personnel personalul companiei - obiecte de tip Person. Folosim operatorul yield pentru a parcurge această colecție:

class Person
{
   public string Name { get; }
   public Person(string name) => Name = name;
}

class Company
{
   Person[] personnel;
   public Company(Person[] personnel) => this.personnel = personnel;
   public int Length => personnel.Length;
   public IEnumerator<Person> GetEnumerator()
   {
       for (int i = 0; i < personnel.Length; i++)
       {
           yield return personnel[i];
       }
   }
}

Metoda GetEnumerator() reprezintă un iterativ. Și când vom parcurge obiectul Company în ciclul foreach, va fi apelat yield return personnel[i]. La apelul operatorului yield return, se va memora poziția curentă.

Iar când ciclul foreach va trece la următoarea iterație pentru a obține un nou obiect, iterativul va începe execuția de la acea poziție.

În programul principal, în ciclul foreach, parcurgerea are loc datorită implementării iterativului:

var people = new Person[]
{
   new Person("Tom"),
   new Person("Bob"),
   new Person("Sam")
};
var microsoft = new Company(people);

foreach (Person employee in microsoft)
{
   Console.WriteLine(employee.Name);
}

Deși în implementarea iterativului în metoda GetEnumerator() am folosit parcurgerea array-ului într-un ciclu for, nu este obligatoriu să facem asta. Putem pur și simplu să definim câteva apeluri ale operatorului yield return:

public IEnumerator<Person> GetEnumerator()
{
   yield return personnel[0];
   yield return personnel[1];
   yield return personnel[2];
}

În acest caz, la fiecare apel al operatorului yield return, iterativul va memora și poziția curentă și, la apelurile ulterioare, va începe de la acea poziție.

Iterativ denumit

Mai sus, pentru crearea iterativului am folosit metoda GetEnumerator. Dar operatorul yield poate fi folosit în orice metodă, doar că metoda respectivă trebuie să returneze un obiect al interfeței IEnumerable. Astfel de metode mai sunt numite iteratoare denumite.

Să creăm un astfel de iterativ denumit în clasa Company și să-l folosim:

class Person
{
   public string Name { get; }
   public Person(string name) => Name = name;
}

class Company
{
   Person[] personnel;
   public Company(Person[] personnel) => this.personnel = personnel;
   public int Length => personnel.Length;
   public IEnumerable<Person> GetPersonnel(int max)
   {
       for (int i = 0; i < max; i++)
       {
           if (i == personnel.Length)
           {
               yield break;
           }
           else
           {
               yield return personnel[i];
           }
       }
   }
}

Iterativul definit aici - metoda IEnumerable GetPersonnel(int max) - acceptă ca parametru numărul maxim de obiecte returnate. În timpul execuției programului, se poate întâmpla ca valoarea sa să fie mai mare decât lungimea array-ului personnel.

Pentru a evita o eroare, se folosește operatorul yield break. Acest operator întrerupe execuția iterativului.

Aplicarea iterativului:

var people = new Person[]
{
   new Person("Tom"),
   new Person("Bob"),
   new Person("Sam")
};
var microsoft = new Company(people);

foreach (Person employee in microsoft.GetPersonnel(5))
{
   Console.WriteLine(employee.Name);
}

Apelul microsoft.GetPersonnel(5) va returna un set de nu mai mult de 5 obiecte Person. Dar, deoarece avem doar trei astfel de obiecte, în metoda GetPersonnel, după trei operații, va fi apelat operatorul yield break.

← Lecția anterioară Lecția următoare →