枚举可枚举

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 个值相比,原理节省了大量的执行时间和空间。