Linq选择奇怪的效果

时间:2017-11-08 17:31:26

标签: c# linq

我在下面的程序中注意到了奇怪的Linq select行为:

IEnumerable<string> s = new List<string>() { "1", "2", "3" };
var i = s.Select(url =>
    {
        Console.WriteLine(url);
        url = string.Format("--{0}--",url);
        return url;
    }
);

Console.WriteLine("done with selector");
foreach (string f in i)
{
    Console.WriteLine("f is {0}", f);
}

输出是:

1
f is --1--
2
f is --2--
3
f is --3--

我期待输出为:

1
2
3
f is --1--
f is --2--
f is --3--

如何解释这种奇怪的行为?它是某种代码优化吗?

3 个答案:

答案 0 :(得分:3)

您的代码完美地说明了延迟执行在LINQ中的工作方式:

  • 首先打印行"done with selector",因为Select“记住”计算序列的信息,而不是实际计算它
  • 来自循环内SelectWriteLine内的lambda的行是交错的,因为序列是在你继续枚举时生成的
  • 如果你在序列中间跳出循环,lambda的其余打印输出将永远不会出现。

如果您在ToList之后添加Select,您的代码将“急切地”生成序列,从而产生您期望的输出(demo)。

答案 1 :(得分:1)

试试这个。

    IEnumerable<string> s = new List<string>() { "1", "2", "3" };
    var i = s.Select(url =>
        {
            Console.WriteLine(url);
            url = string.Format("--{0}--",url);
            return url;
        }
    ).ToList();

    Console.WriteLine("done with selector");
    foreach (string f in i)
    {
        Console.WriteLine("f is {0}", f);
    }

由于延迟执行,第一行中的lambda表达式只会在第二行中迭代结果时执行。

此行为完全正确。

Here是一个非常好的解释。

答案 2 :(得分:0)

您期望的是查询执行并填充您定义查询的变量i

这种行为可以通过在选择之后强制转换.ToList()来强制(并且通常在编写得不太好的代码中不必要):

IEnumerable<string> s = new List<string>() { "1", "2", "3" };
var i = s.Select(url =>
    {
        Console.WriteLine(url);
        url = string.Format("--{0}--",url);
        return url;
    }
).ToList();

Console.WriteLine("done with selector");
foreach (string f in i)
{
    Console.WriteLine("f is {0}", f);
} 

但很少需要。

此行为类似于在c#中使用yield和yield return关键字时所遇到的行为。你可能值得一点研究它们。