A C# foreach ciklus egy igazán elegáns eszköz a gyűjtemények elemeinek bejárására. Használata egyszerűnek tűnik: foreach (var item in collection) { ... }
. De mi is rejlik valójában az ‘in’ kulcsszó mögött? Milyen adattípusok fogadhatók el itt? És hogyan működik mindez a színfalak mögött?
Az ‘in’ kulcsszó titkai
Az ‘in’ kulcsszó után álló objektumnak egy gyűjteménynek kell lennie. Pontosabban, olyan típusnak, amely implementálja az IEnumerable
vagy IEnumerable<T>
interfészek valamelyikét. Ez a lényeg! Ezek az interfészek teszik lehetővé, hogy a foreach ciklus tudja, hogyan kell „végigmenni” az elemeken.
Nézzük meg közelebbről:
IEnumerable
: Ez az alap interfész. Definíciója szerint egyGetEnumerator()
metódust tartalmaz, ami egyIEnumerator
objektumot ad vissza. AzIEnumerator
felelős a gyűjtemény elemeinek sorrendjében történő eléréséért.IEnumerable<T>
: Ez az általánosított (generikus) verzió. Ugyancsak tartalmaz egyGetEnumerator()
metódust, de ez egyIEnumerator<T>
objektumot ad vissza. A<T>
itt a gyűjteményben tárolt elemek típusát jelöli.
Tehát, bármilyen típusú gyűjteményt használhatunk a foreach ciklusban, amely implementálja ezeket az interfészeket. Ez magában foglalja a beépített tömböket (int[]
, string[]
, stb.), a List<T>
, a Dictionary<TKey, TValue>
, a HashSet<T>
és még sok más gyűjteményt.
Példák a gyakorlatban
Nézzünk néhány példát, hogy lássuk, hogyan is működik ez a valóságban:
// Tömb bejárása
int[] szamok = { 1, 2, 3, 4, 5 };
foreach (int szam in szamok)
{
Console.WriteLine(szam);
}
// Lista bejárása
List<string> nevek = new List<string> { "Alice", "Bob", "Charlie" };
foreach (string nev in nevek)
{
Console.WriteLine(nev);
}
// Dictionary bejárása
Dictionary<string, int> pontszamok = new Dictionary<string, int>
{
{ "Alice", 100 },
{ "Bob", 80 },
{ "Charlie", 90 }
};
foreach (KeyValuePair<string, int> pontszam in pontszamok)
{
Console.WriteLine($"{pontszam.Key}: {pontszam.Value}");
}
A fenti példákban a foreach ciklus automatikusan használja az IEnumerator
objektumot, hogy egyesével lekérje a gyűjtemény elemeit. A var
kulcsszó használata a ciklusváltozó típusának automatikus meghatározását teszi lehetővé, ami a legtöbb esetben a legkényelmesebb megoldás. Persze, a példákban látható módon expliciten is megadhatjuk a típust (pl. int szam
, string nev
).
Mi történik a színfalak mögött?
Fontos megérteni, hogy a foreach ciklus valójában csak egy „szintaktikai cukorka”. A fordító a háttérben egy while
ciklussá alakítja át, ami az IEnumerator
interfészt használja.
Például a következő kód:
foreach (int szam in szamok)
{
Console.WriteLine(szam);
}
A fordító által generált kód valami ilyesmi lesz:
IEnumerator<int> enumerator = szamok.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
int szam = enumerator.Current;
Console.WriteLine(szam);
}
}
finally
{
if (enumerator is IDisposable)
{
((IDisposable)enumerator).Dispose();
}
}
Láthatjuk, hogy a fordító lekéri az enumerátort, majd a MoveNext()
metódussal lépked végig a gyűjteményen. A Current
tulajdonság adja vissza az aktuális elemet. A try-finally
blokk pedig biztosítja, hogy az enumerátor felszabaduljon, még akkor is, ha kivétel keletkezik.
Saját gyűjtemény készítése, ami használható foreach-el
Ha saját gyűjteményt szeretnénk készíteni, ami használható a foreach ciklusban, akkor implementálnunk kell az IEnumerable
vagy az IEnumerable<T>
interfészeket. Ez magában foglalja a GetEnumerator()
metódus implementálását, ami egy saját IEnumerator
implementációt ad vissza. Ez az IEnumerator
implementáció felelős a gyűjtemény elemeinek sorrendjében történő eléréséért.
Egy egyszerű példa:
public class SzamGyujtemeny : IEnumerable<int>
{
private int[] _szamok;
public SzamGyujtemeny(int[] szamok)
{
_szamok = szamok;
}
public IEnumerator<int> GetEnumerator()
{
return new SzamEnumerator(_szamok);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private class SzamEnumerator : IEnumerator<int>
{
private int[] _szamok;
private int _pozicio = -1;
public SzamEnumerator(int[] szamok)
{
_szamok = szamok;
}
public int Current
{
get
{
if (_pozicio == -1 || _pozicio >= _szamok.Length)
{
throw new InvalidOperationException();
}
return _szamok[_pozicio];
}
}
object IEnumerator.Current => Current;
public void Dispose() { }
public bool MoveNext()
{
_pozicio++;
return (_pozicio < _szamok.Length);
}
public void Reset()
{
_pozicio = -1;
}
}
}
//Használat:
SzamGyujtemeny gyujtemeny = new SzamGyujtemeny(new int[] { 10, 20, 30 });
foreach (int szam in gyujtemeny)
{
Console.WriteLine(szam);
}
Ez a kód bemutatja, hogyan hozhatunk létre egy saját gyűjteményt, ami a foreach ciklusban használható. A SzamGyujtemeny
osztály implementálja az IEnumerable<int>
interfészt, a SzamEnumerator
pedig az IEnumerator<int>
interfészt.
Vélemény
Személy szerint a foreach ciklus az egyik kedvenc elemem a C#-ban. Rendkívül olvashatóvá és tömörré teszi a kódot, amikor gyűjteményekkel dolgozunk. Az, hogy a fordító a háttérben egy while
ciklussá alakítja át, nem rontja az élményt, sőt, segít megérteni, hogyan is működik a dolog a mélyben. Fontos tudni, hogy nagy, komplex gyűjtemények esetén érdemes odafigyelni a teljesítményre, mivel a foreach
használata bizonyos esetekben lassabb lehet, mint egy egyszerű for
ciklus indexeléssel. Azonban a legtöbb esetben a kód olvashatósága és karbantarthatósága fontosabb szempont, mint a mikroszkopikus teljesítménybeli különbségek. Én a legtöbb esetben a foreach-t preferálom.
„A kód olvashatósága legalább olyan fontos, mint a futási sebessége. A foreach ciklus ezt a célt kiválóan szolgálja.”
Összegzés
A foreach ciklus egy erőteljes és kényelmes eszköz a C#-ban a gyűjtemények elemeinek bejárására. Az ‘in’ kulcsszó után olyan típusnak kell állnia, amely implementálja az IEnumerable
vagy IEnumerable<T>
interfészek valamelyikét. Érdemes megérteni, hogyan működik a foreach ciklus a színfalak mögött, hogy hatékonyabban tudjuk használni és optimalizálni a kódunkat.
Remélhetőleg ez a cikk segített eloszlatni a foreach ciklus körüli rejtélyeket! Ha bármilyen kérdésed van, ne habozz feltenni!