LINQ的延期执行,但如何?

时间:2014-11-04 15:43:56

标签: c# .net linq lazy-evaluation

这一定非常简单。但无论如何我会问它,因为我认为其他人也会挣扎。为什么以下简单的LINQ查询不会始终使用新的变量值执行,而不是始终使用第一个?

static void Main(string[] args)
{
    Console.WriteLine("Enter something:");
    string input = Console.ReadLine();       // for example ABC123
    var digits = input.Where(Char.IsDigit);  // 123
    while (digits.Any())
    {
        Console.WriteLine("Enter a string which doesn't contain digits");
        input = Console.ReadLine();         // for example ABC
    }
    Console.WriteLine("Bye");
    Console.ReadLine();
}

在注释示例中,它将进入循环,因为输入ABC123包含数字。但即使您输入ABC之类的内容也永远不会离开它,因为digits仍然是123

那么为什么LINQ查询不会评估新的input - 值,但始终是第一个?

我知道我可以通过这个额外的一行修复它:

while (digits.Any())
{
    Console.WriteLine("Enter a string which doesn't contain digits");
    input = Console.ReadLine();          
    digits = input.Where(Char.IsDigit);  // now it works as expected
}

或 - 更优雅 - 直接在循环中使用查询:

while (input.Any(Char.IsDigit))
{
    // ...
}

6 个答案:

答案 0 :(得分:40)

不同之处在于您正在更改input变量的值,而不是变量引用的对象的内容 ...所以digits仍指原始收藏品。

将此与此代码进行比较:

List<char> input = new List<char>(Console.ReadLine());
var digits = input.Where(Char.IsDigit);  // 123
while (digits.Any())
{
    Console.WriteLine("Enter a string which doesn't contain digits");
    input.Clear();
    input.AddRange(Console.ReadLine());
}

这一次,我们正在修改input引用的集合的内容 - 并且digits实际上是对该集合的视图,我们可以看到改变。

答案 1 :(得分:10)

您要为input分配新值,但digits序列仍然来自input的初始值。换句话说,当您执行digits = input.Where(Char.IsDigit)时,会捕获input变量的当前值,而不是变量本身。为input分配新值对digits无效。

答案 2 :(得分:6)

这一行:

input.Where(Char.IsDigit)

相当于:

Enumerable.Where(input, Char.IsDigit)

因此,input的值作为.Where查询的来源传递,而不是参考传递给input

您提出的第一个解决方案有效,因为它在先前的行上使用了新分配的input值。

答案 3 :(得分:4)

可枚举数字是指创建可枚举项时input包含的字符串的副本。它没有对input变量的引用,并且更改input中存储的值不会导致枚举的实现使用新值。

请记住,Where是一种静态扩展方法,并接受您将其作为参数调用的对象。

答案 4 :(得分:4)

这几乎是一个评论,但包含结构化代码,因此我将其作为答案提交。

以下对代码的轻微修改将起作用:

  Console.WriteLine("Enter something:");
  string input = Console.ReadLine();       // for example ABC123
  Func<bool> anyDigits = () => input.Any(Char.IsDigit);  // will capture 'input' as a field
  while (anyDigits())
  {
    Console.WriteLine("Enter a string which doesn't contain digits");
    input = Console.ReadLine();         // for example ABC
  }
  Console.WriteLine("Bye");
  Console.ReadLine();

此处inputFunc<bool>类型的委托捕获(封闭)。

答案 5 :(得分:2)

我只是回答其他好答案的精确度,关于延迟执行

即使尚未评估LINQ查询(使用.Any()),查询内部也始终引用变量的初始内容。即使在 之后评估了某个新变量影响了LINQ查询,初始内容也不会发生变化,延迟执行将使用查询一直引用的初始内容:

var input = "ABC123";
var digits = input.Where(Char.IsDigit);
input = "NO DIGIT";
var result = digits.ToList();   // 3 items