gpt4 book ai didi

c# - 链式 LINQ 语句的效率如何?

转载 作者:行者123 更新时间:2023-12-04 01:27:49 29 4
gpt4 key购买 nike

在这种情况下,我想迭代集合中除最后 2 个元素之外的所有元素。

假设我采用了一种奇怪的方式,例如 x.Reverse().Skip(2).Reverse()

每个 LINQ 操作是否会有效地生成一个嵌套迭代器或导致枚举等,还是比这更聪明?在这种情况下会发生什么?


说明:这只是您可能会看到的链式 LINQ 语句的一个示例,其中开发人员喜欢简短而强大的代码,而不太考虑性能 - 也许他们是计算机科学专业的学生,​​这似乎是“最聪明”的解决方案.我不是在问如何解决这个特定的例子

最佳答案

首先是的,它正在创建一个“迭代器”,并且在您在 foreach 中具体化查询之前实际上不会进行任何迭代或调用 ToList在上面。当您这样做时,发生的迭代次数基于基础类型。 Reverse将为您提供的任何源创建一个缓冲区数组并向后迭代它。如果来源是ICollection<T>然后它将使用它的 CopyTo填充数组的方法通常会在恒定时间内产生连续数据的单个大容量副本。如果它不是 ICollection<T>然后它会将源迭代到缓冲区中,然后向后迭代它。考虑到这一点,下面是您的特定查询在迭代时会发生什么。

第一个最后一个Reverse将开始迭代其源(不是 ICollection<T> )。

然后 Skip将开始迭代其来源

然后第一个 Reverse 将执行 CopyTo如果它的来源是 ICollection<T>或者它会将源迭代到一个缓冲区数组中,并根据需要调整大小。

然后第一个 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 实现一次之前迭代源(除非它是 ICollectionICollection<T>,在这种情况下它将只使用 Count 属性)然后当您实现它时它会再次迭代它。

下面是使用不同代码和结果输出的相同示例。

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 TakeCount将是首选(除非源可能在创建查询和实现查询之间发生变化),但如果两者都不是,您可能更喜欢双 Reverse避免迭代源两次(特别是如果源可以在您创建查询和实际实现它之间发生变化,因为大小也可能发生变化),但这必须根据完成的总迭代次数和内存使用量的增加进行加权。

关于c# - 链式 LINQ 语句的效率如何?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61548111/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com