0Pricing
C# Academy · Lekcja

Niestandardowe typy wyliczalne

Implementuj IEnumerable za pomocą iteratorów.

Niestandardowe typy wyliczalne to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 4 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.

Tworzenie własnego typu enumerowalnego

Implementacja IEnumerable<T> w klasie sprawia, że można jej używać w pętli foreach. W połączeniu z metodami iteratorów pozwala to tworzyć własne kolekcje i zakresy przy użyciu bardzo niewielkiej ilości kodu.

Implementowanie GetEnumerator za pomocą yield

Najprostszym sposobem zaimplementowania IEnumerable<T> jest napisanie GetEnumerator jako iteratora korzystającego z yield return. Kompilator sam tworzy enumerator.

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

public class IntRange : IEnumerable<int>
{
    private readonly int _start, _count;
    public IntRange(int start, int count) { _start = start; _count = count; }

    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i < _count; i++)
            yield return _start + i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new IntRange(5, 4))
            Console.WriteLine(n);
    }
}

Dlaczego potrzebne są dwie metody GetEnumerator?

IEnumerable<T> dziedziczy po niegenerycznym IEnumerable, dlatego należy zaimplementować obie wersje. Jawnie zaimplementowana wersja niegeneryczna po prostu deleguje wywołanie do wersji generycznej.

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

public class Letters : IEnumerable<char>
{
    public IEnumerator<char> GetEnumerator()
    {
        yield return 'x';
        yield return 'y';
        yield return 'z';
    }
    // Required because IEnumerable<T> extends IEnumerable
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var c in new Letters())
            Console.WriteLine(c);
    }
}

Hermetyzowanie własnej logiki

Własny typ enumerowalny może ukrywać złożone reguły generowania za przejrzystym typem. W tym przykładzie klasa generuje ciąg Fibonacciego o wybranej długości.

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

public class Fibonacci : IEnumerable<int>
{
    private readonly int _count;
    public Fibonacci(int count) { _count = count; }

    public IEnumerator<int> GetEnumerator()
    {
        int a = 0, b = 1;
        for (int i = 0; i < _count; i++)
        {
            yield return a;
            int next = a + b; a = b; b = next;
        }
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new Fibonacci(8))
            Console.WriteLine(n);
    }
}

Opakowywanie wewnętrznej kolekcji

Własny typ enumerowalny często opakowuje wewnętrzne dane, udostępniając iterowanie i jednocześnie kontrolując sposób dodawania lub filtrowania elementów.

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

public class EvenBag : IEnumerable<int>
{
    private readonly List<int> _items = new List<int>();
    public void Add(int n) { if (n % 2 == 0) _items.Add(n); }

    public IEnumerator<int> GetEnumerator() => _items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var bag = new EvenBag();
        bag.Add(1); bag.Add(2); bag.Add(3); bag.Add(4);
        foreach (var n in bag) Console.WriteLine(n);
    }
}

Własne typy enumerowalne współpracują z LINQ

Każdy typ implementujący IEnumerable<T> natychmiast zyskuje pełną funkcjonalność LINQ. Własną kolekcję można filtrować, przekształcać i agregować bez dodatkowego wysiłku.

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

public class IntRange : IEnumerable<int>
{
    private readonly int _start, _count;
    public IntRange(int start, int count) { _start = start; _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i < _count; i++) yield return _start + i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var range = new IntRange(1, 10);
        Console.WriteLine(range.Where(n => n % 3 == 0).Sum());
    }
}

Każda pętla foreach otrzymuje nowy enumerator

Ponieważ GetEnumerator jest wywoływane przy każdej iteracji, własny typ enumerowalny można enumerować wielokrotnie, a każde przejście rozpoczyna się od początku.

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

public class IntRange : IEnumerable<int>
{
    private readonly int _count;
    public IntRange(int count) { _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 1; i <= _count; i++) yield return i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var r = new IntRange(3);
        foreach (var n in r) Console.Write(n);
        Console.WriteLine();
        foreach (var n in r) Console.Write(n); // fresh again
        Console.WriteLine();
    }
}

Generyczny własny typ enumerowalny

Typ enumerowalny może być generyczny, aby przechowywać elementy dowolnego typu. W tym przykładzie prosty pierścień udostępnia elementy w kolejności rozpoczynającej się od wybranego przesunięcia.

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

public class Ring<T> : IEnumerable<T>
{
    private readonly T[] _items;
    private readonly int _start;
    public Ring(T[] items, int start) { _items = items; _start = start; }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = 0; i < _items.Length; i++)
            yield return _items[(_start + i) % _items.Length];
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var ring = new Ring<string>(new[] { "a", "b", "c", "d" }, 2);
        foreach (var s in ring) Console.WriteLine(s);
    }
}

Leniwe własne typy enumerowalne

Ponieważ GetEnumerator samo jest iteratorem, własny typ enumerowalny jest domyślnie leniwy: elementy są tworzone dopiero wtedy, gdy odbiorca je pobiera.

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

public class Squares : IEnumerable<int>
{
    private readonly int _count;
    public Squares(int count) { _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 1; i <= _count; i++)
        {
            Console.WriteLine("computing " + i);
            yield return i * i;
        }
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new Squares(3))
            Console.WriteLine("got " + n);
    }
}

Kiedy tworzyć własny typ enumerowalny

Własny typ enumerowalny warto utworzyć, gdy kolejność iteracji lub logika generowania stanowi kluczową część typu, na przykład podczas przechodzenia po drzewie, obsługi stronicowanego źródła danych lub generowania ciągu matematycznego. W przeciwnym razie wystarczy zwykła metoda iteratora.

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

public class Countdown : IEnumerable<int>
{
    private readonly int _from;
    public Countdown(int from) { _from = from; }
    public IEnumerator<int> GetEnumerator()
    {
        for (int i = _from; i >= 0; i--) yield return i;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        foreach (var n in new Countdown(5)) Console.WriteLine(n);
    }
}

Proszę spróbować samodzielnie

Proszę utworzyć własny typ enumerowalny generujący ciąg geometryczny, a następnie użyć na nim LINQ. Państwa typ będzie bezpośrednio współpracować z całym ekosystemem.

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

public class Geometric : IEnumerable<int>
{
    private readonly int _start, _ratio, _count;
    public Geometric(int start, int ratio, int count) { _start = start; _ratio = ratio; _count = count; }
    public IEnumerator<int> GetEnumerator()
    {
        int value = _start;
        for (int i = 0; i < _count; i++) { yield return value; value *= _ratio; }
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class Program
{
    public static void Main()
    {
        var seq = new Geometric(1, 2, 6);
        Console.WriteLine(string.Join(", ", seq));
        Console.WriteLine("sum: " + seq.Sum());
    }
}

Szybkie sprawdzenie

Proszę przypomnieć sobie kontrakt IEnumerable.

Podsumowanie

Własny typ enumerowalny implementuje IEnumerable<T>, dzięki czemu współpracuje z foreach i LINQ.

  • Należy zaimplementować GetEnumerator jako iterator za pomocą yield return.
  • Należy również udostępnić jawnie zaimplementowaną niegeneryczną wersję GetEnumerator.
  • Każda iteracja otrzymuje nowy enumerator.
  • Iteracja jest domyślnie leniwa.

Często zadawane pytania

Czy lekcja „Niestandardowe typy wyliczalne” jest bezpłatna?

Tak — pełny tekst „Niestandardowe typy wyliczalne” 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 „Niestandardowe typy wyliczalne”?

Implementuj IEnumerable za pomocą 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 4 z 4.

Ile czasu zajmuje lekcja „Niestandardowe typy wyliczalne”?

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