首先是的,它正在创建一个“迭代器”,并且实际上并没有进行任何迭代,直到您在 foreach 中实现查询或在其上调用 ToList。当您这样做时,发生的迭代次数取决于基础类型。 Reverse 将为您提供的任何源创建一个缓冲区数组并向后迭代它。如果源是ICollection<T>,那么它将使用其CopyTo 方法来填充数组,这通常会在恒定时间内生成连续数据的单个大容量副本。如果它不是ICollection<T>,那么它会将源迭代到缓冲区中,然后向后迭代它。考虑到这一点,您的特定查询在迭代时会发生什么。
首先最后一个Reverse 将开始迭代其源(不是ICollection<T>)。
然后Skip 将开始迭代其源代码
如果源是ICollection<T>,那么第一个 Reverse 将执行 CopyTo,或者它将源迭代到一个缓冲区数组中,并根据需要调整大小。
然后第一个 Reverse 将向后迭代其缓冲区数组
然后,Skip 将跳过前两个并产生其余的结果
然后最后一个 Reverse 将获取结果并将它们添加到其缓冲区数组中并根据需要调整其大小。
最后最后一个 Reverse 将向后迭代缓冲区数组。
因此,如果您要处理的是 ICollecion<T>,那就是一个 CopyTo,然后对所有值进行 1 次迭代,然后对除 2 个以外的所有值进行 1 次迭代。如果不是ICollection<T>,那基本上是值的 3 次迭代(实际上最后一次迭代是除了 2 之外的所有迭代)。无论哪种方式,它也在过程中使用了两个中间数组。
为了证明查询在实现之前不会进行迭代,您可以查看此示例
void Main()
{
var query = MyValues().Reverse().Skip(2).Reverse();
Console.WriteLine($"After query before materialization");
var results = query.ToList();
Console.WriteLine(string.Join(",", results));
}
public IEnumerable<int> MyValues()
{
for(int i = 0; i < 10; i ++)
{
Console.WriteLine($"yielding {i}");
yield return i;
}
}
哪个产生输出
After query before materialization
yielding 0
yielding 1
yielding 2
yielding 3
yielding 4
yielding 5
yielding 6
yielding 7
yielding 8
yielding 9
0,1,2,3,4,5,6,7
与您拥有x.Take(x.Count() - 2) 的另一个示例相比,它将在您为Count 实现一次之前迭代源(除非它是ICollection 或ICollection<T>,在这种情况下它将只使用@987654341 @property) 然后它会在你实现它时再次迭代它。
这是相同的示例,但代码和结果不同。
void Main()
{
var x = MyValues();
var query = x.Take(x.Count() - 2);
Console.WriteLine($"After query before materialization");
var results = query.ToList();
Console.WriteLine(string.Join(",", results));
}
public IEnumerable<int> MyValues()
{
for(int i = 0; i < 10; i ++)
{
Console.WriteLine($"yielding {i}");
yield return i;
}
}
有输出
yielding 0
yielding 1
yielding 2
yielding 3
yielding 4
yielding 5
yielding 6
yielding 7
yielding 8
yielding 9
After query before materialization
yielding 0
yielding 1
yielding 2
yielding 3
yielding 4
yielding 5
yielding 6
yielding 7
0,1,2,3,4,5,6,7
所以哪个更好完全取决于来源。对于ICollection<T> 或ICollection,Take 和Count 将是首选(除非源可能在创建查询和实现查询之间发生变化),但如果这两者都不是你可能更喜欢双Reverse 以避免重复源两次(特别是如果源可以在您创建查询和实际实现查询之间发生变化,因为大小也可能发生变化),但这必须与完成的总迭代次数和内存的增加相权衡用法。