Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Szinte minden megírt programnak meg kell iterálnia egy gyűjteményt. Olyan kódot fog írni, amely egy gyűjtemény minden elemét megvizsgálja.
Iterátor metódusokat is létrehozhat, amelyek olyan metódusok, amelyek iterátort hoznak létre az adott osztály elemeihez. Az iterátor egy olyan objektum, amely bejár egy tárolót, különösen a listákat. Az iterátorok a következő célokra használhatók:
- Művelet végrehajtása a gyűjtemény minden elemén.
- Egyéni gyűjtemény számbavétele.
- LINQ vagy más könyvtárak kiterjesztése.
- Olyan adatfolyam létrehozása, amelyben az adatok iterátori metódusokkal hatékonyan haladnak.
A C#-nyelv funkciókkal rendelkezik a sorozatok létrehozásához és felhasználásához is. Ezek a sorozatok szinkron vagy aszinkron módon hozhatók létre és használhatók fel. Ez a cikk áttekintést nyújt ezekről a funkciókról.
Iterálás foreach használatával
A gyűjtemények számbavétele egyszerű: A foreach kulcsszó számba ad egy gyűjteményt, és egyszer végrehajtja a beágyazott utasítást a gyűjtemény minden eleméhez:
foreach (var item in collection)
{
Console.WriteLine(item?.ToString());
}
Ennyi az egész. Ha egy gyűjtemény összes tartalmát át szeretné iterálni, csak az foreach utasításra van szüksége. Az foreach állítás nem varázslat. A .NET core-kódtárban definiált két általános adapterre támaszkodik a gyűjtemény iterálásához szükséges kód létrehozásához: IEnumerable<T> és IEnumerator<T>. Ezt a mechanizmust az alábbiakban részletesebben ismertetjük.
Mindkét interfész nem általános megfelelőkkel is rendelkezik: IEnumerable és IEnumerator. A modern kódhoz az általános verziókat részesítik előnyben.
Ha egy sorozat aszinkron módon jön létre, az await foreach utasítással aszinkron módon használhatja fel a sorozatot:
await foreach (var item in asyncSequence)
{
Console.WriteLine(item?.ToString());
}
Ha egy sorozat egy System.Collections.Generic.IEnumerable<T>, akkor használjuk a foreach. Ha egy sorozat egy System.Collections.Generic.IAsyncEnumerable<T>, akkor használjuk a await foreach. Az utóbbi esetben a szekvencia aszinkron módon jön létre.
Enumerálási források iterációs metódusokkal
A C# nyelv egy másik nagyszerű funkciója lehetővé teszi az enumerálás forrását létrehozó metódusok létrehozását. Ezeket a metódusokat iterátor metódusoknak nevezzük. Az iterátormetódus meghatározza, hogyan hozhatja létre az objektumokat egy sorrendben, amikor az szükséges. A yield return környezetfüggő kulcsszavak használatával definiálhat egy iterátor metódust.
Ezt a metódust úgy is megírhatja, hogy az egész számok sorozata 0 és 9 között legyen:
public IEnumerable<int> GetSingleDigitNumbers()
{
yield return 0;
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
yield return 6;
yield return 7;
yield return 8;
yield return 9;
}
A fenti kód különböző yield return utasításokat jelenít meg, amelyek kiemelik azt a tényt, hogy több különálló yield return utasítást is használhat egy iterátor-metódusban. Más nyelvi szerkezetekkel is leegyszerűsítheti az iterátor metódus kódját. Az alábbi metódusdefiníció pontosan ugyanazt a számsort állítja elő:
public IEnumerable<int> GetSingleDigitNumbersLoop()
{
int index = 0;
while (index < 10)
yield return index++;
}
Nem kell egyiket vagy a másikat választania. Annyi yield return utasítást használhat, amennyi szükséges ahhoz, hogy kielégítse a metódus igényeit.
public IEnumerable<int> GetSetsOfNumbers()
{
int index = 0;
while (index < 10)
yield return index++;
yield return 50;
index = 100;
while (index < 110)
yield return index++;
}
A fenti példák mindegyike aszinkron megfelelővel rendelkezik. Minden esetben Ön lecserélné a visszatérési típust IEnumerable<T>-ről egy IAsyncEnumerable<T>-re. Az előző példa például a következő aszinkron verzióval rendelkezik:
public async IAsyncEnumerable<int> GetSetsOfNumbersAsync()
{
int index = 0;
while (index < 10)
yield return index++;
await Task.Delay(500);
yield return 50;
await Task.Delay(500);
index = 100;
while (index < 110)
yield return index++;
}
Ez a szinkron és az aszinkron iterátorok szintaxisa. Tekintsünk egy valós példát. Tegyük fel, hogy egy IoT-projekten dolgozik, és az eszközérzékelők nagyon nagy adatstreamet hoznak létre. Ha szeretne áttekintést kapni az adatokról, írhat egy metódust, amely minden N-edik adatelemet kiválaszt. Ez a kis iterátor módszer elvégzi a trükköt:
public static IEnumerable<T> Sample<T>(this IEnumerable<T> sourceSequence, int interval)
{
int index = 0;
foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}
Ha az IoT-eszközről történő olvasás aszinkron sorozatot hoz létre, a metódust az alábbi módszer szerint módosíthatja:
public static async IAsyncEnumerable<T> Sample<T>(this IAsyncEnumerable<T> sourceSequence, int interval)
{
int index = 0;
await foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}
Az iterátormetódusokra egy fontos korlátozás vonatkozik: ugyanabban a metódusban nem lehet egyszerre return utasítást és yield return utasítást használni. A következő kód nem lesz lefordítva:
public IEnumerable<int> GetSingleDigitNumbers()
{
int index = 0;
while (index < 10)
yield return index++;
yield return 50;
// generates a compile time error:
var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
return items;
}
Ez a korlátozás általában nem jelent problémát. Választhat, hogy mindenhol a yield return elemet használja a módszer során, vagy az eredeti módszert több kisebb részre osztja, amelyek közül néhány a return, míg mások a yield return elemet használják.
Az utolsó metódust kismértékben módosíthatja, hogy mindenhol használhassa yield return :
public IEnumerable<int> GetFirstDecile()
{
int index = 0;
while (index < 10)
yield return index++;
yield return 50;
var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
foreach (var item in items)
yield return item;
}
Néha a helyes válasz az iterátor metódus két különböző módszerre való felosztása. Az egyik, amelyik használjareturn, és a másik, amelyik használjayield return. Vegyük figyelembe azt a helyzetet, amikor egy logikai argumentum alapján üres gyűjteményt vagy az első öt páratlan számot szeretnénk visszaadni. A következő két módszerként írhatja ezt:
public IEnumerable<int> GetSingleDigitOddNumbers(bool getCollection)
{
if (getCollection == false)
return new int[0];
else
return IteratorMethod();
}
private IEnumerable<int> IteratorMethod()
{
int index = 0;
while (index < 10)
{
if (index % 2 == 1)
yield return index;
index++;
}
}
Tekintse meg a fenti módszereket. Az első a standard return utasítással egy üres gyűjteményt, vagy a második metódus által létrehozott iterátort ad vissza. A második metódus az yield return utasítást használja a kért sorozat létrehozásához.
Mélyebben elmélyülünk foreach
Az foreach utasítás egy szabványos kifejezéssé bővül, amely a gyűjtemény összes elemének iterálására használja az IEnumerable<T> és IEnumerator<T> a felületeket. Emellett az erőforrások nem megfelelő kezelésével minimalizálja a fejlesztők által tapasztalt hibákat.
A fordító az foreach első példában látható hurkot az alábbi szerkezethez hasonlóra fordítja le:
IEnumerator<int> enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
A fordító által létrehozott konkrét kód bonyolultabb, és kezeli azokat a helyzeteket, ahol a GetEnumerator() által visszaadott objektum implementálja a IDisposable interfészt. A teljes bővítés a következőhöz hasonló kódot hoz létre:
{
var enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of enumerator.
}
}
A fordító az első aszinkron mintát az alábbi szerkezethez hasonlóra fordítja le:
{
var enumerator = collection.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of async enumerator.
}
}
Az enumerátor kezelésének módja a enumerator típus jellemzőitől függ. Az általános szinkron esetben a finally záradék a következőképpen bővül:
finally
{
(enumerator as IDisposable)?.Dispose();
}
Az általános aszinkron eset a következőre bővül:
finally
{
if (enumerator is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
}
Ha azonban a típus enumerator egy lezárt típus, és nem történik implicit átalakítás a enumeratorIDisposable típusról vagy IAsyncDisposablea típusra, a finally záradék üres blokktá bővül:
finally
{
}
Ha van implicit átalakítás enumerator típusból IDisposable típusba, és enumerator egy nem-null értékű értéktípus, a finally kifejezés a következőre bővül:
finally
{
((IDisposable)enumerator).Dispose();
}
Szerencsére nem kell emlékeznie ezekre a részletekre. Az foreach utasítás kezeli az összes apró részletet számodra. A fordító létrehozza a megfelelő kódot ezen szerkezetek bármelyikéhez.