Mennyi mindent használunk, de igazából lehet, hogy fogalmunk sincs róla, hogy mire is jó pontosan, és mikor is érdemes használni. A miértről nem is beszélve…
Mit mesél erről a Gemini?
A .NET-ben (C#) a yield kulcsszó pontosan ugyanazt a célt szolgálja, mint a Pythonban: IEnumerable<T> vagy IAsyncEnumerable<T> típusú adatfolyamokat hozhatsz vele létre úgy, hogy a kód lusta kiértékeléssel (lazy evaluation), elemenként adja vissza az eredményeket.
C#-ban két formája létezik:
yield return <érték>: Visszaad egy elemet, majd felfüggeszti a metódus futását a következő elem kéréséig.yield break: Azonnal leállítja az iterációt (megszakítja a generálást).
Mire jó C#-ban?
1. Memóriatakarékosság nagy adathalmazoknál
Ha nem List<T>-et adsz vissza, nem kell a teljes gyűjteményt lefoglalni a RAM-ban.
C#
using System;
using System.Collections.Generic;
// PÉLDA 1: Hagyományos megközelítés (Memóriatakaréktalan)
public List<int> GetNumbersList()
{
var list = new List<int>();
for (int i = 0; i < 1_000_000; i++)
{
list.Add(i); // Mind az 1 milló elem belekerül a RAM-ba
}
return list;
}
// PÉLDA 2: yield használata (Memóriatakarékos)
public IEnumerable<int> GetNumbersYield()
{
for (int i = 0; i < 1_000_000; i++)
{
yield return i; // Mindig csak az aktuális számot generálja le
}
}
2. Szűrés és adatfeldolgozás yield break-kel
A yield break használatával feltételhez kötheted az adatfolyam végét anélkül, hogy bonyolult feltételkezelést kellene írnod a ciklus köré:
C#
public IEnumerable<string> GetActiveUsers(List<string> users)
{
foreach (var user in users)
{
if (user == "ADMIN_STOP")
{
yield break; // Azonnal leállítja az elemek kiadását
}
if (!string.IsNullOrEmpty(user))
{
yield return user;
}
}
}
3. Óriási fájlok feldolgozása soronként
Nem kell a gigabájtos fájlokat egyszerre a memóriába tölteni (File.ReadAllLines helyett):
C#
using System.IO;
using System.Collections.Generic;
public IEnumerable<string> ReadLargeFile(string filePath)
{
using (StreamReader reader = new StreamReader(filePath))
{
string? line;
while ((line = reader.ReadLine()) != null)
{
yield return line; // Mindig csak egyetlen sort tart a memóriában
}
}
}
// Használat:
foreach (string line in ReadLargeFile("huge_log.txt"))
{
if (line.Contains("ERROR"))
{
Console.WriteLine(line);
}
}
4. Aszinkron adatfolyamok (C# 8.0+: IAsyncEnumerable<T>)
Modern .NET-ben hálózati kéréseknél vagy adatbázis-lekérdezéseknél aszinkron módon is használhatod:
C#
public async IAsyncEnumerable<int> FetchDataAsync()
{
for (int i = 1; i <= 5; i++)
{
await Task.Delay(1000); // Pl. hálózati várakozás szimulálása
yield return i;
}
}
// Használat:
await foreach (var item in FetchDataAsync())
{
Console.WriteLine(item); // Másodpercenként jelenik meg egy elem
}
Hagyományos visszatérés vs. yield C#-ban
| Szempont | List<T> / Tömb | yield return (IEnumerable<T>) |
| Kiértékelés ideje | Azonnali (A metódus hívásakor lefut a teljes ciklus) | Lusta / Halasztott (Csak akkor fut, amikor a foreach kéri a következő elemet) |
| Memóriahasználat | Magas ($O(N)$ méretű lista a RAM-ban) | Minimális ($O(1)$ állandó memóriagény) |
| Sebesség (első elem) | Lassú (meg kell várni a teljes lista felépítését) | Azonnali (az első elem kérésére rögtön visszatér) |
Gondoltam, megkérdezem ugyanezt Typescript esetén.
Igen, pontosan ugyanígy működik TypeScriptben és JavaScriptben is.
A TypeScript a JavaScript generátor függvényeit (Generator Functions) használja, amelyeket az function* (csillaggal jelölt) szintaxisról lehet felismerni.
A működési elv teljesen megegyezik a Python és a C# esetében látottakkal: a yield felfüggeszti a függvény futását, megőrzi a belső állapotot, és lusta kiértékeléssel (lazy evaluation) adja vissza az elemeket egy Generator objektumon keresztül.
Alapvető példa TypeScriptben
A generátor függvény visszatérési típusa Generator<T, TNext TReturn,>, ahol a T a yield-elt értékek típusa.
TypeScript
// A csillag (function*) jelzi, hogy ez egy generátor függvény
function* szamGeneraltor(): Generator<number> {
yield 1;
yield 2;
yield 3;
}
// Használat for...of ciklussal:
for (const szam of szamGeneraltor()) {
console.log(szam); // Kiírja egymás után: 1, 2, 3
}
Mire jó TypeScriptben?
1. Memóriatakarékos és végtelen sorozatok
Nem kell előre tömbbe gyűjteni az elemeket:
TypeScript
function* vegtelenAzonositoGeneral(): Generator<string> {
let id = 1;
while (true) {
yield `ID_${id++}`;
}
}
const idGen = vegtelenAzonositoGeneral();
console.log(idGen.next().value); // "ID_1"
console.log(idGen.next().value); // "ID_2"
console.log(idGen.next().value); // "ID_3"
2. Adatfolyamok delegálása (yield*)
A TypeScriptben létezik a yield* utasítás is, amellyel átadhatod a vezérlést egy másik generátornak vagy tömbnek (bejárható objektumnak):
TypeScript
function* elsoResz(): Generator<number> {
yield 1;
yield 2;
}
function* teljesSorozat(): Generator<number> {
yield* elsoResz(); // Delegálja az elemeket az elsoResz-ből
yield* [3, 4, 5]; // Tömb elemeit is ki tudja adni egymás után
}
console.log([...teljesSorozat()]); // [1, 2, 3, 4, 5]
3. Aszinkron generátorok (yield + async)
Ahogy C#-ban az IAsyncEnumerable, TypeScriptben és JS-ben is létezik az AsyncGenerator (async function*). Ez hálózati adatok, lapozott API-k vagy adatfolyamok (stream) feldolgozásánál nagyon hasznos:
TypeScript
async function* adatokLetolteseAPIrol(): AsyncGenerator<string> {
const oldalak = [1, 2, 3];
for (const oldal of oldalak) {
// Szimulált hálózati kérés (API hívás)
const valasz = await fetch(`https://api.example.com/data?page=${oldal}`);
const adat = await valasz.text();
yield adat; // Mindig csak az aktuális oldal adatait adja vissza
}
}
// Használat for await...of ciklussal:
async function feldolgozas() {
for await (const oldalAdat of adatokLetolteseAPIrol()) {
console.log("Megérkezett egy oldal:", oldalAdat);
}
}
Összehasonlítás
| Nyelv | Függvény jelölése | Kulcsszó | Bejárási felület |
| C# | IEnumerable<T> Get() | yield return | foreach / LINQ |
| Python | def get(): | yield | for item in gen |
| TypeScript | function* get(): Generator<T> | yield | for...of / next() |
Vélemény, hozzászólás?
Hozzászólás küldéséhez be kell jelentkezni.