为什么IEnumerable <thing>选择每次运行?</thing>

时间:2012-07-25 22:08:55

标签: c# linq

我花了一秒钟看看为什么我的应用程序表现糟糕。我所做的只是暂停调试器两次,我找到了它。

为什么每次都运行我的代码有一个实际的原因吗?我知道防止这种情况的唯一方法是在最后添加ToArray()。我想我需要修改所有代码并确保它们返回数组?

在线演示http://ideone.com/EUfJN

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
class Test
{

    static void Main()
    {
        string[] test = new string[] { "a", "sdj", "bb", "d444"};
        var expensivePrint = false;
        IEnumerable<int> ls = test.Select(s => { if (expensivePrint) { Console.WriteLine("Doing expensive math"); } return s.Length; });
        expensivePrint = true;
        foreach (var v in ls)
        {
            Console.WriteLine(v);
        }
        Console.WriteLine("If you dont think it does it everytime, lets try it again");
        foreach (var v in ls)
        {
            Console.WriteLine(v);
        }
    }
}

输出

Doing expensive math
1
Doing expensive math
3
Doing expensive math
2
Doing expensive math
4
If you dont think it does it everytime, lets try it again
Doing expensive math
1
Doing expensive math
3
Doing expensive math
2
Doing expensive math
4

7 个答案:

答案 0 :(得分:4)

Enumerables懒惰地评估(仅在需要时)。在select之后添加.ToList(),它将强制进行评估。

答案 1 :(得分:3)

选择会导致迭代器被迭代。

如果构建结果的成本很高,你可以.ToList()一次结果,然后继续使用该列表。

List<int> resultAsList = ls.ToList();
// Use resultAsList in each of the foreach statements

答案 2 :(得分:3)

LINQ有懒惰的评估方法,Select就是其中之一。

问题是你正在使用foreach两次,它会打印两次值。

答案 3 :(得分:1)

构建查询时

IEnumerable<int> ls = test.Select(s => { if (expensivePrint) { Console.WriteLine("Doing expensive math"); } return s.Length; });

它实际上并不执行并缓存结果,因为您显然期待。这被称为“违背执行”。

它只是构建查询。在查询上调用foreach语句时,实际执行查询。

如果您在查询中致电ToList()ToArray()Sum()Average()或此类运营商,则会立即执行此操作。

如果要保留查询结果,最好的办法是通过调用ToList()ToArray()将其缓存在数组或列表中,并枚举此列表或数组而不是而不是构造的查询。

答案 4 :(得分:1)

这是Linq的延期执行。如果您需要简明扼要的完整解释,请阅读:

http://weblogs.asp.net/dixin/archive/2010/03/16/understanding-linq-to-objects-6-deferred-execution.aspx

答案 5 :(得分:1)

请参阅Enumerable.Select

的文档
  

此方法通过使用延迟执行来实现。立即返回值是一个对象,它存储执行操作所需的所有信息。在通过直接调用其GetEnumerator方法或在Visual C#中使用foreach或在Visual Basic中使用For Each来枚举对象之前,不会执行此方法表示的查询。

通过迭代Select方法的结果,执行查询。 foreach是迭代该结果的一种方法。 ToArray是另一个。


  

每次运行我的代码是否有实际原因?

是的,如果结果没有推迟,那么将执行比必要更多的迭代:

IEnumerable<string> query = Enumerable.Range(0, 100000)
  .Select(x => x.ToString())
  .Where(s => s.Length == 6)
  .Take(5);

答案 6 :(得分:0)

我建议您使用.ToArray()return int[]将为您提供更好的效果

int[]的原因,因为它将立即声明和创建,以及List<T>将在运行时逐个创建

int[] array = test.Select(s =>
{
   if (expensivePrint)
   {
     Console.WriteLine("Doing expensive math");
   }
   return s.Length;
}).ToArray();