0Pricing
C# Academy · Lekcja

Semantyka leniwej ewaluacji

Poznaj odroczone wykonywanie iteratorów.

Semantyka leniwej ewaluacji to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.

Wykonanie odroczone

Iteratory i zapytania LINQ korzystają z wykonania odroczonego: samo zdefiniowanie zapytania prawie nic nie robi. Praca rozpoczyna się dopiero wtedy, gdy wynik jest rzeczywiście enumerowany, element po elemencie.

Nic nie jest wykonywane przed enumeracją

Wywołanie metody iteratora nie wykonuje jej ciała. Ciało zaczyna działać dopiero wtedy, gdy foreach lub inny odbiorca pobierze pierwszą wartość.

using System;
using System.Collections.Generic;

public class Program
{
    static IEnumerable<int> Numbers()
    {
        Console.WriteLine("-- iterator body started --");
        yield return 1;
        yield return 2;
    }

    public static void Main()
    {
        var seq = Numbers();
        Console.WriteLine("query created, body not run yet");
        foreach (var n in seq) Console.WriteLine(n);
    }
}

Wartości są tworzone pojedynczo

Każda iteracja wykonuje tylko tyle kodu iteratora, ile potrzeba do utworzenia kolejnej wartości. Ten model oparty na pobieraniu ogranicza zużycie pamięci nawet w przypadku bardzo dużych sekwencji.

using System;
using System.Collections.Generic;

public class Program
{
    static IEnumerable<int> Trace()
    {
        for (int i = 1; i <= 3; i++)
        {
            Console.WriteLine("producing " + i);
            yield return i;
        }
    }

    public static void Main()
    {
        foreach (var n in Trace())
            Console.WriteLine("consumed " + n);
    }
}

Ponowna enumeracja uruchamia wykonanie ponownie

Odroczona sekwencja nie ma zbuforowanego wyniku. Jej dwukrotne enumerowanie wykonuje iterator dwa razy, co ma znaczenie, jeśli źródło się zmienia lub operacja jest kosztowna.

using System;
using System.Collections.Generic;

public class Program
{
    static int _calls = 0;
    static IEnumerable<int> Counter()
    {
        _calls++;
        yield return _calls;
    }

    public static void Main()
    {
        var seq = Counter();
        foreach (var n in seq) Console.WriteLine(n);
        foreach (var n in seq) Console.WriteLine(n);
        Console.WriteLine("total runs: " + _calls);
    }
}

Odczytywanie aktualnego stanu

Ponieważ wykonanie jest odroczone, iterator odczytuje źródło w chwili enumeracji, a nie w chwili definiowania. Zmiany wprowadzone po zdefiniowaniu zapytania będą widoczne.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var list = new List<int> { 1, 2 };
        IEnumerable<int> doubled = Double(list);
        list.Add(3); // added before enumeration
        foreach (var n in doubled) Console.WriteLine(n);
    }

    static IEnumerable<int> Double(IEnumerable<int> nums)
    {
        foreach (var n in nums) yield return n * 2;
    }
}

Wymuszanie natychmiastowego wykonania

Aby utworzyć migawkę wyników, należy zmaterializować sekwencję za pomocą ToList() lub ToArray(). Spowoduje to jednokrotne wykonanie iteratora i zapisanie wartości, uniezależniając wynik od późniejszych zmian źródła.

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var list = new List<int> { 1, 2 };
        List<int> snapshot = Double(list).ToList(); // executed now
        list.Add(99);
        Console.WriteLine(string.Join(",", snapshot));
    }

    static IEnumerable<int> Double(IEnumerable<int> nums)
    {
        foreach (var n in nums) yield return n * 2;
    }
}

Wczesne kończenie oszczędza pracę

Ponieważ odbiorcy pobierają wartości leniwie, operacja taka jak wyszukiwanie pierwszego dopasowania może zakończyć się wcześniej. Producent nigdy nie oblicza wartości, o które nie poproszono.

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    static IEnumerable<int> Numbers()
    {
        for (int i = 1; ; i++)
        {
            Console.WriteLine("checking " + i);
            yield return i;
        }
    }

    public static void Main()
    {
        int firstBig = Numbers().First(n => n > 3);
        Console.WriteLine("found " + firstBig);
    }
}

Odroczone wyjątki

Wyjątek wewnątrz iteratora nie jest zgłaszany podczas wywołania metody, lecz dopiero wtedy, gdy enumeracja dotrze do linii powodującej błąd. Może to zaskoczyć, jeśli oczekują Państwo walidacji na początku.

using System;
using System.Collections.Generic;

public class Program
{
    static IEnumerable<int> Risky(int divisor)
    {
        yield return 10 / divisor; // throws only during enumeration
    }

    public static void Main()
    {
        var seq = Risky(0);
        Console.WriteLine("no error yet");
        try { foreach (var n in seq) Console.WriteLine(n); }
        catch (DivideByZeroException) { Console.WriteLine("caught during enumeration"); }
    }
}

Leniwe potoki łatwo łączyć

Łączenie leniwych operatorów nie powoduje ich wielokrotnego wykonywania. Każda wartość przepływa przez cały potok jeden raz, na żądanie, co jest wydajne i oszczędza pamięć.

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var result = Enumerable.Range(1, 1000000)
            .Where(n => n % 2 == 0)
            .Select(n => n * n)
            .Take(3);
        foreach (var n in result) Console.WriteLine(n);
    }
}

Kiedy materializować

Sekwencję należy zmaterializować, gdy będzie enumerowana wielokrotnie, gdy źródło może się zmienić lub gdy trzeba przechwycić wyniki przed zwolnieniem zasobu. W przeciwnym razie warto pozostać przy wykonaniu leniwym, aby oszczędzać pamięć i pracę.

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var evens = Enumerable.Range(1, 10).Where(n => n % 2 == 0).ToList();
        // Safe to enumerate many times now
        Console.WriteLine("count: " + evens.Count);
        Console.WriteLine("sum: " + evens.Sum());
    }
}

Proszę spróbować samodzielnie

Proszę bezpośrednio zaobserwować wykonanie odroczone: zdefiniować zapytanie, zmodyfikować źródło, a następnie sprawdzić, że zmiany pojawiają się w wynikach dopiero podczas enumeracji.

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var source = new List<int> { 1, 2, 3 };
        var query = source.Where(n => n > 1); // not executed yet
        source.Add(10);
        source.Remove(2);
        // Executes now, reflecting all changes
        Console.WriteLine(string.Join(", ", query));
    }
}

Szybkie sprawdzenie

Proszę przeanalizować wykonanie odroczone.

Podsumowanie

Obliczanie leniwe (odroczone) wykonuje sekwencję dopiero podczas enumeracji.

  • Zdefiniowanie zapytania nie wykonuje żadnej pracy; robi to dopiero jego użycie.
  • Ponowna enumeracja ponownie uruchamia iterator.
  • Źródła są odczytywane podczas enumeracji, dlatego późniejsze zmiany są widoczne.
  • Wyjątki są zgłaszane podczas enumeracji.
  • W razie potrzeby należy użyć ToList()/ToArray(), aby zmaterializować sekwencję.

Często zadawane pytania

Czy lekcja „Semantyka leniwej ewaluacji” jest bezpłatna?

Tak — pełny tekst „Semantyka leniwej ewaluacji” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Semantyka leniwej ewaluacji”?

Poznaj odroczone wykonywanie iteratorów. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C# Academy?

Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Semantyka leniwej ewaluacji”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?

Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Metody iteratorów z yield return
  2. yield break i wcześniejsze zakończenie
  3. Semantyka leniwej ewaluacji
  4. Niestandardowe typy wyliczalne
← Powrót do C# Academy