CodeGym /Kurslar /C# SELF /Sayılabilərlik üçün kontrakt:

Sayılabilərlik üçün kontrakt: IEnumerable<T>

C# SELF
Səviyyə , Dərs
Mövcuddur

1. Giriş

Yəqin ki, artıq hiss etmisən, C#-da müxtəlif kolleksiyaların oxşar cəhətləri var. Məsələn, demək olar ki, istənilən kolleksiyanı foreach dövrü ilə gəzə bilərik:


var names = new List<string> { "Anya", "Boris", "Vika" };

// Diqqət — kod həm List, həm massiv, hətta HashSet ilə işləyir!
foreach (var name in names)
{
    Console.WriteLine(name);
}

Bunun sirri nədir? Bütün müasir kolleksiyalar IEnumerable<T> interfeysini reallaşdırır — yəni bu bir "kontrakt"dır, hansı ki, kolleksiya elementləri bir-bir, ardıcıl qaytarmağı bacarır.

Təsəvvür elə bir şirkət var, orda istənilən işçi kolleksiyası üçün (istər şöbə, istər layihə komandası, istər korporativ tədbir iştirakçıları siyahısı) belə bir qayda var: əgər obyekt "IEnumerable" interfeysini reallaşdırırsa, deməli, sən həmişə bütün işçiləri müəyyən ardıcıllıqla gəzə bilərsən, fərqi yoxdur "onlar necə saxlanılır", əsas odur ki, onları bir-bir əldə etmək olur.

2. IEnumerable<T> interfeysi — içində nə var?

Gəlin baxaq, bu interfeys .NET-də necə görünür:


public interface IEnumerable<out T>
{
    IEnumerator<T> GetEnumerator();
}

Burda cəmi bir metod var — GetEnumerator, hansı ki, IEnumerator<T> tipində obyekt qaytarır. Bu obyekt "sayma" prosesinə cavabdehdir: o bilir, indi hansı elementdəyik, növbətiyə necə keçmək olar, nə vaxt bitir.

Qısa desək: əgər bir class (və ya kolleksiya) IEnumerable<T> reallaşdırırsa, deməli, onu foreach dövründə gəzə bilərsən, elementləri bir-bir əldə edə bilərsən — və fərqi yoxdur, içəridə necə qurulub: istər siyahı, istər hash-table, istərsə də hard diskdə saxlanılsın.

Mini-sxem


Kolleksiya (məsələn, List<T>)
    |
    v
IEnumerable<T>
    |
    v
IEnumerator<T> (əsl "sayan" obyekt)

3. Praktika və kodun universallığı

Belə kontraktın böyük üstünlüyü — universallıqdır. Əgər funksiya və ya metod input kimi IEnumerable<T> qəbul edirsə, deməli, o istənilən kolleksiya tipi ilə işləyə bilər — massivlərdən və siyahılardan tutmuş hash-set, queue və hətta öz kolleksiyan da daxil!

Məsələn, elementlərin cəmini hesablamaq üçün funksiya:


// İstənilən IEnumerable<int> dəstəkləyən int toplularının cəmini hesablamaq olar
int Sum(IEnumerable<int> numbers)
{
    int result = 0;
    foreach (var n in numbers)
        result += n;
    return result;
}

// List<int> ilə işləyir
var list = new List<int> { 1, 2, 3 };
Console.WriteLine(Sum(list));

// Massivlə də işləyir!
int[] array = { 4, 5, 6 };
Console.WriteLine(Sum(array));

// Hətta filtrasiya edən metodun nəticəsi ilə də işləyir
Console.WriteLine(Sum(list.Where(x => x % 2 == 0))); // LINQ istifadə edirik

Həyatdan nümunə: universal metodlar

Təsəvvür elə, sənə istənilən string kolleksiyasında ən uzun sözü tapmaq üçün utilit yazmaq lazımdır. IEnumerable<string> sayəsində bunu istənilən mənbə üçün edə bilərsən — massiv, siyahı, filtrasiya metodlarının nəticəsi və s.:


string FindLongest(IEnumerable<string> words)
{
    string longest = "";
    foreach (var word in words)
        if (word.Length > longest.Length)
            longest = word;
    return longest;
}

İstənilən uyğun toplu ilə istifadə etmək olar.

4. Niyə foreach dövrü IEnumerable<T> ilə işləyir?

Tez-tez verilən sual: ümumiyyətlə, niyə foreach dövrü istənilən kolleksiyanı "başa düşür"? Cavab sadədir: C# kompilyatoru class-da GetEnumerator metodunu axtarır və gözləyir ki, elə bir obyekt qaytaracaq ki, orda MoveNext() metodu və Current property-si var. Bu da standart interfeysdir — IEnumerator<T>.

Belə yanaşma sayəsində, hətta öz class-ını yaza bilərsən ki, elementləri ardıcıl "verir" (məsələn, Fibonacci ardıcıllığı generatoru), və əgər o IEnumerable<int> reallaşdırırsa, onunla foreach dövründə adi siyahı kimi işləmək olar.

5. Enumerator nədir və necə işləyir?

İstənilən IEnumerable<T> reallaşdıran kolleksiyanın içində xüsusi bir “sayacı” gizlənir — Enumerator (və ya elmi dildə: IEnumerator<T> interfeysini reallaşdıran obyekt). Məhz o, foreach dövründə kolleksiyanın elementlərini bir-bir "daşıyır".

IEnumerator<T> interfeysi

Standart Enumerator bunları bacarır:


public interface IEnumerator<T> : IDisposable
{
    T Current { get; }         // Cari element
    bool MoveNext();           // Növbəti elementə keç
    void Reset();              // Əvvələ qayıt (çox az istifadə olunur)
}

İçəridə necə işləyir?

  • MoveNext() — "göstəricini" növbəti elementə aparır və true qaytarır, əgər element varsa. Əgər element qalmayıbsa — false qaytarır.
  • Current — cari elementi verir (indi hansı elementdədirsə).
  • Reset() — Enumerator-u əvvələ qaytarır (praktikada demək olar ki, istifadə olunmur).
  • Dispose() — resursları azad edir (fayllarla və ya şəbəkə ilə işləyən kolleksiyalar üçün lazımdır).

Nümunə: foreach "arxa planda" necə işləyir

Sən belə yazanda:


var numbers = new List<int> { 1, 2, 3 };
foreach (var n in numbers)
    Console.WriteLine(n);

Əslində kompilyator bunu təxminən belə kod halına salır:


var numbers = new List<int> { 1, 2, 3 };

// "Sayacı" alırıq
var enumerator = numbers.GetEnumerator();
while (enumerator.MoveNext())
{
    var n = enumerator.Current;
    Console.WriteLine(n);
}
// Kompilyator avtomatik Dispose() çağırır using blokunda (əgər enumerator IDisposable reallaşdırırsa)

Vacibdir: əgər kolleksiya xarici resurslarla işləyirsə (məsələn, fayllar, verilənlər bazası), Enumerator sayma bitəndə onları avtomatik azad edə bilər.

Vizual sxem


Başlanğıc -> GetEnumerator() -> Enumerator
         |
         v
  MoveNext() -> Current
         |
         v
  MoveNext() -> Current
         |
        ...
         |
         v
  MoveNext() == false -> son

6. IEnumerable və massivlər, siyahılar, set-lər: kim kimə qohumdur?

Gəlin baxaq, .NET-in hansı standart konteynerləri bu interfeysi reallaşdırır:

Kolleksiya tipi IEnumerable<T> reallaşdırır? foreach dövrü ilə gəzilə bilər?
Massiv (
int[]
)
List<T>
Dictionary<TKey, V>
✅ (cütlüklər, açarlar, dəyərlər üzrə)
HashSet<T>
Queue<T>
Stack<T>

Hətta string (string) də adi IEnumerable reallaşdırır, yəni hər bir simvol üzrə də gəzə bilərsən.

7. Öz Enumerable-ını reallaşdırmaq

Maraqlı tapşırıq: gəlin minimal kolleksiya düzəldək, hansı ki, 0-dan N-ə qədər cüt ədədləri saxlayır və IEnumerable<int> reallaşdırır. Onda onu dövr ilə gəzə və LINQ ilə istifadə edə bilərik.


// "Gəzilə bilən" kolleksiya class-ı
class EvenNumbers : IEnumerable<int>
{
    private int max;

    public EvenNumbers(int max)
    {
        this.max = max;
    }

    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i <= max; i += 2)
            yield return i; // Enumerator üçün xüsusi magiya
    }

    // Geri uyğunluq üçün qeyri-generik IEnumerable interfeysinin açıq reallaşdırılması
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

// İstifadə:
var evens = new EvenNumbers(10);
foreach(var e in evens)
    Console.Write($"{e} "); // 0 2 4 6 8 10

Əsas məqam: əgər class-ın IEnumerable<T> reallaşdırırsa — avtomatik olaraq onu .NET-in bir çox aləti ilə uyğun edirsən: LINQ, foreach, enumerable qəbul edən metodlar və s.

8. Tipik səhvlər və nüanslar

Bəzən təcrübəsiz proqramçılar düşünür ki, IEnumerable<T> — ayrıca bir kolleksiyadır, hansı ki, elementləri var. Əslində — bu sadəcə "söz"dür ki, əgər biz saymağa başlasaq, bizə elementləri bir-bir "verəcəklər".

Əgər sənə təsadüfi indekslə müraciət lazımdırsa (myList[5]), Add və ya Remove kimi metodlardan istifadə etmək istəyirsənsə — IEnumerable<T> interfeysi burada kömək etməyəcək. Bu yalnız ardıcıl keçid üçündür!

Səhv: kolleksiyanı sayarkən dəyişməyə çalışmaq. Məsələn:


foreach (var item in myList)
{
    if (item < 0)
        myList.Remove(item); // TƏHLÜKƏLİ! InvalidOperationException
}

Daha yaxşısı, əvvəlcə silmək üçün ayrıca siyahı düzəltmək, ya da yeni kolleksiya yaradan metodlardan istifadə etməkdir:


// Təhlükəsiz yol — yeni kolleksiyanı əl ilə düzəldirik
var newList = new List<int>();
foreach (var item in myList)
{
    if (item >= 0)
        newList.Add(item);
}
myList = newList;

// Və ya indekslərlə tərsinə silirik
for (int i = myList.Count - 1; i >= 0; i--)
{
    if (myList[i] < 0)
        myList.RemoveAt(i);
}
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION