Kategória: Typescript

  • Tipikus interjú kérdés: yield

    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

    SzempontList<T> / Tömbyield return (IEnumerable<T>)
    Kiértékelés idejeAzonnali (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álatMagas ($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

    NyelvFüggvény jelöléseKulcsszóBejárási felület
    C#IEnumerable<T> Get()yield returnforeach / LINQ
    Pythondef get():yieldfor item in gen
    TypeScriptfunction* get(): Generator<T>yieldfor...of / next()