枚举可枚举
IEnumerable <T>接口是所有通用枚举器的基本接口,是理解 LINQ 的典型部分。它的核心是代表序列。
此底层接口由所有泛型集合继承,例如 Collection <T> , Array , List <T> , Dictionary <TKey,TValue> Class 和 HashSet <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 个值相比,原理节省了大量的执行时间和空间。