列舉可列舉

IEnumerable <T>介面是所有通用列舉器的基本介面,是理解 LINQ 的典型部分。它的核心是代表序列。

此底層介面由所有泛型集合繼承,例如 Collection <T>ArrayList <T>Dictionary <TKey,TValue> ClassHashSet <T>

除了表示序列之外,任何從 IEnumerable <T>繼承的類都必須提供 IEnumerator <T>。列舉器公開了可列舉的迭代器,這兩個相互關聯的介面和思想是列舉可列舉的源頭。

列舉可列舉的是一個重要的短語。可列舉只是一個如何迭代的結構,它不包含任何具體化的物件。例如,在排序時,可列舉可以保持欄位的條件進行排序,但是使用 .OrderBy() 本身將返回 IEnumerable <T>,它只知道如何排序。使用將實現物件的呼叫(如迭代集合)稱為列舉(例如 .ToList())。列舉過程將使用可列舉的定義如何在系列中移動並返回相關物件(按順序,過濾,投影等)。

只有列舉了列舉後,它才會導致物件的具體化,這就像時間複雜度 (與系列大小相關應該花多長時間)和空間複雜度(它應該使用多少與系列大小相關的空間)等指標一樣被測量。

建立自己的繼承自 IEnumerable <T>的類可能有點複雜,具體取決於需要列舉的基礎系列。通常,最好使用現有的通用集合之一。也就是說,也可以從 IEnumerable <T>介面繼承,而不必將已定義的陣列作為底層結構。

例如,使用 Fibonacci 系列作為基礎序列。請注意,對 Where 的呼叫只是構建了一個 IEnumerable,直到列舉的列舉被認為是任何值都已實現的列舉。

void Main()
{
    Fibonacci Fibo = new Fibonacci();
    IEnumerable<long> quadrillionplus = Fibo.Where(i => i > 1000000000000);
    Console.WriteLine("Enumerable built");
    Console.WriteLine(quadrillionplus.Take(2).Sum());
    Console.WriteLine(quadrillionplus.Skip(2).First());

    IEnumerable<long> fibMod612 = Fibo.OrderBy(i => i % 612);
    Console.WriteLine("Enumerable built");
    Console.WriteLine(fibMod612.First());//smallest divisible by 612
}

public class Fibonacci : IEnumerable<long>
{
    private int max = 90;

    //Enumerator called typically from foreach
    public IEnumerator GetEnumerator() {
        long n0 = 1;
        long n1 = 1;
        Console.WriteLine("Enumerating the Enumerable");
        for(int i = 0; i < max; i++){
            yield return n0+n1;
            n1 += n0;
            n0 = n1-n0;
        }
    }
    
    //Enumerable called typically from linq
    IEnumerator<long> IEnumerable<long>.GetEnumerator() {
        long n0 = 1;
        long n1 = 1;
        Console.WriteLine("Enumerating the Enumerable");
        for(int i = 0; i < max; i++){
            yield return n0+n1;
            n1 += n0;
            n0 = n1-n0;
        }
    }
}

輸出

Enumerable built
Enumerating the Enumerable
4052739537881
Enumerating the Enumerable
4052739537881
Enumerable built
Enumerating the Enumerable
14930352

第二組(fibMod612)中的強度即使我們呼叫了整個 Fibonacci 數的整數,因為使用 .First() 只取一個值,時間複雜度為 O(n),因為只需要 1 個值在排序演算法執行期間進行了比較。這是因為我們的列舉器只詢問了 1 個值,因此不需要實現整個可列舉。如果我們使用 .Take(5) 代替 .First(),則列舉器會要求 5 個值,並且最多需要實現 5 個值。與需要訂購整套然後取前 5 個值相比,原理節省了大量的執行時間和空間。