A foreach ciklus, ahogy korábban említettem, csak olyan osztályok esetén működik, amelyek megvalósítják az IEnumerable felületet. Az IEnumerable felület egyetlen egy metódust tartalmaz:
IEnumerator GetEnumerator()
A függvény egy IEnumerator leszármazott elemet kell, hogy visszaadjon. Ezen felület a következő elemekkel rendelkezik:
object Current { get; }
A ciklus belső állapotát tárolja, az aktuális elemet kell neki visszaadnia.
bool MoveNext();
A következő elemre lépést valósítja meg. Igaz értéket kell visszaadnia, ha a továbblépés megtörtént a következő elemre, hamis értéket pedig akkor, ha a továbblépés nem lehetséges, mert elértük az iterált kollekció végét.
void Reset();
Visszaállítja a ciklust az alap, kezdő állapotra.
Az alábbi példa egy saját osztály esetén mutatja be az IEnumerable implementációját:
using System.Collections;
namespace PeldaIenumerable
{
class SzuperCsapat: IEnumerable
{
public SzuperCsapat() { } //üres konstruktor
public IEnumerator GetEnumerator()
{
return new SzuperCsapatEnumerator();
}
}
class SzuperCsapatEnumerator : IEnumerator
{
private int _index;
public SzuperCsapatEnumerator()
{
//azért, hogy a 0. elem is ki legyen írva
//mivel a MoveNext() a ciklus elején hívódik!
_index = -1;
}
public object Current
{
get
{
switch (_index)
{
case 0:
return "Hannibal";
case 1:
return "Szépfiú";
case 2:
return "Murdock";
case 3:
return "Rosszfiú";
default:
return null;
}
}
}
public bool MoveNext()
{
_index++;
return _index < 4;
}
public void Reset()
{
_index = -1;
}
}
}
A létrehozott Enumerator ezek után foreach ciklussal igen kézenfekvően használható:
using System;
namespace PeldaIenumerable
{
class Program
{
static void Main(string[] args)
{
var ATeam = new SzuperCsapat();
Console.WriteLine("A szuper csapat tagjai:");
foreach (var tag in ATeam)
{
Console.WriteLine(tag);
}
Console.ReadKey();
}
}
}
A program kimenete
A szuper csapat tagjai:
Hannibal
Szépfiú
Murdock
Rosszfiú
Az implementáció jelen esetben kicsit eltúlzott, nem éppen gyakorlatias megoldás. Ennek az oka az, hogy a kódot az IEnumerable működésére és az IEnumerator működésére szerettem volna kihegyezni. Gyakorlatban persze ennél sokkal egyszerűbben is megvalósítható a dolog. Például így:
using System.Collections;
namespace PeldaIenumerableJobb
{
class SzuperCsapat : IEnumerable
{
private string[] _tagok;
public SzuperCsapat()
{
_tagok = new string[] { "Hannibal", "Szépfiú", "Murdock", "Rosszfiú" };
}
public IEnumerator GetEnumerator()
{
return _tagok.GetEnumerator();
}
}
}
A javított implementáció azt használja ki, hogy a tömbök esetén implementálva van az IEnumerable felület, így egyszerűen csak elég a belső tömb Enumerator típusát visszaadnia az osztály GetEnumerator metódusának.
A yield kulcsszó
Az IEnumerator implementációk során nem midig tudunk „csalni” a fentebb bemutatott módon, azonban ekkor sem kell megijednünk az IEnumerable megvalósításakor. A C# 2.0-ás változata óta létezik a yield kulcsszó, amelyet direkt erre a célra implementáltak. Az alábbi mintaprogram az első mintaprogram átdolgozásával mutatja be a yield működését:
using System.Collections;
namespace PeldaYield
{
class SzuperCsapat : IEnumerable
{
public SzuperCsapat() { }
public IEnumerator GetEnumerator()
{
for (int i=0; i<4; i++)
{
switch (i)
{
case 0:
yield return "Hannibal";
break;
case 1:
yield return "Szépfiú";
break;
case 2:
yield return "Murdock";
break;
case 3:
yield return "Rosszfiú";
break;
default:
yield return null;
break;
}
}
}
}
}
A yield a visszatérési értéket úgy módosítja, hogy a futó for ciklus belső állapotát eltárolja és a következő metódus híváskor (foreach következő elemre lép) a mentett állapottól folytatja a ciklus végrehajtását. Ez kitűnően alkalmas Lazy loading megvalósítására.
A Lazy loading lényege, hogy csak a használat pillanatában töltjük be az elemeket. A tömböt alkalmazó implementáció esetén a tömb példányosításakor létrejöttek az elemek, foglalva a memóriát. Ez nem ideális, ha a program életciklusában viszonylag későn van szükségünk az elemekre.