Enumeratii
Pe langa colectii, in C# exista si conceptul de enumeratie (enumerable) care incapsuleaza conceptul de structura de date iterabila, adica o enumeratie poate fi parcursa iterativ. Toate colectiile implementeaza interfata IEnumerable<T> si sunt enumeratii. Enumeratiile expun un enumerator care in C# este echivalentul unui iterator pentru parcurgerea colectiei. Un enumerator/iterator este un obiect care este legat de o instanta a unei colectii si urmareste pe ce pozitie a ramas in acea colectie pentru a implementa directive de parcurgere cum ar fi foreach.
Pentru ca toate colectiile implementeaza interfata IEnumerable<T> (pentru dictionare/mapuri e IEnumerable<KeyValuePair<TKey, TValue>>) vin la pachet cu operatii de fluxuri (stream-uri) de date sub forma de LINQ (Language INtegrated Queries).
Obtinand iteratorul pentru o colectie prin interfata IEnumerable<T> se pot crea functii pentru diferite operatii generice asupra fluxului de date care rezulta din acea colectie, aceste functii sunt metode de LINQ si sunt metode de extensie pentru IEnumerable<T> (vom invata despre metode de extensie pe viitor).
Totusi, inainte trebuie vazut cum putem obtine un IEnumerable si fara un obiect. Putem folosi cuvantul cheie yield intr-o functie care returneaza IEnumerable parametrizata de un tip atunci cand facem return cu o valoare de acel tip ca in exemplul de mai jos.
public static IEnumerable<int> Generate(int start, int end)
{
for (var i = start; i < end; ++i)
{
Console.WriteLine("Returning from generator {0}", i);
yield return i;
}
}
La foreach codul este echivalent cu urmatorul cod:
// Daca lasam ToList() functia se va executa complet, altfel executia va fi lazy.
var list = Generate(1, 23).ToList();
// Urmatoarea bucata de cod este echivalenta cu un foreach.
using var enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.Write("{0} ", item);
}
Ce se intampla cand se apeleaza functia cu yield, se va returna un IEnumerable iar atunci cand e apelat MoveNext pe enumerator pentru extragerea unei valori se executa functia pana se returneaza o valoare. Atunci se suspenda executia functiei pana la urmatoarea iteratie cand se pleaca din punctul anterior si continua executia pana la urmatorul return sau pana la terminarea executiei functiei cu yield.
Trebuie inteles ca iterarea peste IEnumerable este lazy, adica valorile la iteratie sunt calculate pe loc, nu inainte de inceperea iteratiei. Acest lucru are avantajul ca computatii nenecesare nu vor fi procesate, dezavantajul este ca se poate folosi doar o data un IEnumerable returnat prin functie cu yield, altfel se pot folosi metode de a concretiza acel IEnumerable intr-o colectie cum este ToList, ToHashSet sau ToArray.
Cand folositi un enumerator generat printr-o colectie nu modificati colectia cand se itereaza prin ea, pot aparea erori si programul va crapa daca se incearca acest lucru. Este valabil pentru orice tip de iterator din orice limbaj care suporta iteratoare.