为什么不延迟执行缓存迭代值?

时间:2009-06-09 12:16:47

标签: c# .net linq language-design

采用以下代码,改编自this question

//Borrowed from another question because its a simpler example of what happened to me.
IEnumerable<char> query = "Not what you might expect";
foreach(char vowel in "aeiou")
{
     query = query.Where(c => c != vowel);
}
foreach (char Output in query)
{
    System.Out.WriteLine(Output);
}

这只会从查询字符集中删除“u”。核心问题与c子句中的Where变量在第二次foreach之前未被评估的事实有关。我的问题是:

1)为什么第一个foreach生成的委托在构建时不会捕获c的每个值?是否有一些情况我不知道哪里不是理想的行为?

2)如果它没有捕获c的值,那么当查询实际运行时,该值是否仍然在第二个foreach的范围内?在我看来,如果它没有存储传入的变量的值,那么尝试解析第二个foreach的语句将会失败,因为变量c显然超出了范围。

我不明白'当它在范围内时,使用我们在这个变量上看到的最后一个值'对于这种情况来说是一个很好的设计决策,并且希望有人可以对这个问题有所了解。

3 个答案:

答案 0 :(得分:7)

正在捕捉vowel;尝试:

foreach(char vowel in "aeiou") {
     char tmp = vowel;
     query = query.Where(c => c != tmp);
}

Jon有相关帖子列表here

至于为什么...... foreach被定义为(在ECMA334v4§15.8.4中):

A foreach statement of the form `foreach (V v in x) embedded-statement` is then expanded to:

{
  E e = ((C)(x)).GetEnumerator();
  try {
    V v;
    while (e.MoveNext()) {
      v = (V)(T)e.Current;
      embedded-statement
    }
  }
  finally {
    … // Dispose e
  }
}

请注意,V 之外 while - 这意味着foreach只会一次。我认为C#团队有时会怀疑这种变化的智慧(它在C#1.2规范中是内部 while,这本来可以完全避免这个问题)。也许它最终会改变,但现在它符合规范。

答案 1 :(得分:4)

正如Marc所说,它将vowel作为变量捕获,而不是每种情况下vowel的值。这几乎总是不受欢迎的,但它指定的行为(即编译器遵循语言规范)。每个迭代都有一个元音变量,而不是“新的变量实例”。

请注意,此处不涉及表达式树 - 您正在使用IEnumerable<T>,因此它只是将lambda表达式转换为委托。

有关详细信息,请参阅C#3规范的第7.14.4节。

答案 2 :(得分:1)

我认为你真正想做的事情更像是这样:

IEnumerable<char> query = "Not what you might expect";
query = query.Except("aeiou");

foreach (char Output in query)
{
    System.Out.WriteLine(Output);
}