MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Interfețele IEnumerable și IEnumerator

Așa cum am văzut, baza pentru majoritatea colecțiilor este implementarea interfețelor IEnumerable și IEnumerator. Datorită acestei implementări, putem parcurge obiectele într-un ciclu foreach:

foreach(var item in enumerabil_object)
{
   
}

Colecția care poate fi parcursă trebuie să implementeze interfața IEnumerable.

Interfața IEnumerable are o metodă care returnează o referință la o altă interfață - enumeratorul:

public interface IEnumerable
{
   IEnumerator GetEnumerator();
}

Interfața IEnumerator definește funcționalitatea pentru parcurgerea obiectelor interne din container:

public interface IEnumerator
{
   bool MoveNext(); // mută la următoarea poziție în containerul de elemente
   object Current {get;}  // elementul curent din container
   void Reset(); // resetează la începutul containerului
}

Metoda MoveNext() mută cursorul către elementul curent la următoarea poziție din secvență. Dacă secvența nu s-a terminat, returnează true. Dacă secvența s-a terminat, returnează false.

Proprietatea Current returnează obiectul din secvență către care este mutat cursorul.

Metoda Reset() resetează cursorul poziției la poziția inițială.

Modul exact în care se mută cursorul și cum se obțin elementele depinde de implementarea interfeței. În diverse implementări, logica poate fi construită diferit.

De exemplu, fără a folosi ciclul foreach, putem parcurge un array folosind interfața IEnumerator:

using System.Collections;

string[] people = {"Tom", "Sam", "Bob"};

IEnumerator peopleEnumerator = people.GetEnumerator(); // obținem IEnumerator
while (peopleEnumerator.MoveNext())   // până când returnează false
{
   string item = (string)peopleEnumerator.Current; // obținem elementul la poziția curentă
   Console.WriteLine(item);
}
peopleEnumerator.Reset(); // resetăm cursorul la începutul array-ului

Implementarea IEnumerable și IEnumerator

Să vedem o implementare simplă a IEnumerable printr-un exemplu:

using System.Collections;

Week week = new Week();
foreach (var day in week)
{
   Console.WriteLine(day);
}

class Week : IEnumerable
{
   string[] days = { "Monday", "Tuesday", "Wednesday", "Thursday",
                        "Friday", "Saturday", "Sunday" };
   public IEnumerator GetEnumerator() => days.GetEnumerator();
}

În acest caz, clasa Week, care reprezintă săptămâna și stochează toate zilele săptămânii, implementează interfața IEnumerable. Cu toate acestea, aici am procedat foarte simplu - în loc de a implementa IEnumerator, returnăm pur și simplu în metoda GetEnumerator un obiect IEnumerator pentru array.

public IEnumerator GetEnumerator() => days.GetEnumerator();

Datorită acestui lucru, putem parcurge toate zilele săptămânii într-un ciclu foreach.

Totodată, merită menționat că pentru a parcurge colecția prin foreach, în principiu nu este obligatoriu să implementăm interfața IEnumerable. Este suficient să definim în clasă o metodă publică GetEnumerator care să returneze un obiect IEnumerator. De exemplu:

class Week
{
   string[] days = { "Monday", "Tuesday", "Wednesday", "Thursday",
                       "Friday", "Saturday", "Sunday" };
   public IEnumerator GetEnumerator() => days.GetEnumerator();
}

Cu toate acestea, aceasta a fost destul de simplu - folosim pur și simplu enumeratorul array-ului deja existent. Cu toate acestea, este posibil să fie necesar să definim propria noastră logică de parcurgere a obiectelor. Pentru aceasta, implementăm interfața IEnumerator:

using System.Collections;

class WeekEnumerator : IEnumerator
{
   string[] days;
   int position = -1;
   public WeekEnumerator(string[] days) => this.days = days;
   public object Current
   {
       get
       {
           if (position == -1 || position >= days.Length)
               throw new ArgumentException();
           return days[position];
       }
   }
   public bool MoveNext()
   {
       if (position < days.Length - 1)
       {
           position++;
           return true;
       }
       else
           return false;
   }
   public void Reset() => position = -1;
}

class Week
{
   string[] days = { "Monday", "Tuesday", "Wednesday", "Thursday",
                           "Friday", "Saturday", "Sunday" };
   public IEnumerator GetEnumerator() => new WeekEnumerator(days);
}

Acum clasa Week folosește enumeratorul propriu, WeekEnumerator, care implementează IEnumerator.

Punctul cheie în implementarea enumeratorului este mutarea cursorului pe element. În clasa WeekEnumerator, pentru stocarea poziției curente este definită variabila position. Trebuie să avem în vedere că la început (în starea inițială) cursorul trebuie să indice la poziția condiționată înaintea primului element.

Când se va executa ciclul foreach, acest ciclu va apela mai întâi metoda MoveNext și va muta de fapt cursorul cu o poziție înainte și doar apoi va apela proprietatea Current pentru a obține elementul la poziția curentă.

Apoi în program putem parcurge în mod similar obiectul Week folosind ciclul foreach:

Week week = new Week();
foreach(var day in week)
{
   Console.WriteLine(day);
}

Versiunea generică a IEnumerator

În exemplele de mai sus s-au folosit versiunile non-generic ale interfețelor, dar putem folosi și echivalentele lor generice:

using System.Collections;

class WeekEnumerator : IEnumerator<string>
{
   string[] days;
   int position = -1;
   public WeekEnumerator(string[] days) => this.days = days;
   public string Current
   {
       get
       {
           if (position == -1 || position >= days.Length)
               throw new ArgumentException();
           return days[position];
       }
   }
   object IEnumerator.Current => Current;
   public bool MoveNext()
   {
       if (position < days.Length - 1)
       {
           position++;
           return true;
       }
       else
           return false;
   }
   public void Reset() => position = -1;
   public void Dispose() { }
}

class Week
{
   string[] days = { "Monday", "Tuesday", "Wednesday", "Thursday",
                           "Friday", "Saturday", "Sunday" };
   public IEnumerator<string> GetEnumerator() => new WeekEnumerator(days);
}

În acest caz, implementăm interfața IEnumerator<string>, astfel încât în proprietatea Current trebuie să returnăm un obiect string. În acest caz, la parcurgerea în ciclul foreach, obiectele parcurse vor fi automat de tip string:

Week week = new Week();
foreach (string day in week)
{
   Console.WriteLine(day);
}
← Lecția anterioară Lecția următoare →