我有一个关于两位代码的问题,这些代码在我构建和运行它们时似乎以相同的方式运行但产生不同的结果。
代码A:
String[] colors = {"green", "red", "blue", "brown"};
var query = colors.Where(c => c.Contains("e"));
query = query.Where(c=> c.Contains("n"));
Console.WriteLine(query.Count());
这会产生一个“1”的控制台输出,因为可能剩下的唯一值是“绿色”(包含e和n)。
如果代码被巧妙地改变,结果是不同的
代码B:
String[] colors = {"green", "red", "blue", "brown"};
string s = "e";
var query = colors.Where(c => c.Contains(s));
s = "n";
query = query.Where(c=> c.Contains("n"));
Console.WriteLine(query.Count());
这会产生一个“2”的控制台输出,“查询”中剩下的值是“绿色,棕色”。据我所知,原因是因为我们在之前持有“e”的地方指定了“n”的值。如果我转储“查询”的内容,它将包含“绿色/棕色”,尽管事实上我已经将查询减少到包含“e”的所有值。
如果有人能解释为什么会发生这种情况会很棒,谢谢!
答案 0 :(得分:3)
由于“延期执行”,您会看到不同的结果。即当你致电Where()
时,它实际上 你的收藏品。一旦您尝试访问枚举,它就会返回将做某事的IEnumerable<string>
实现。
因此,在第二个示例中,您在查询lambda中引用变量s
,在执行第一个查询时,您已将变量更改为值"n"
,并且所以两个查询都进行相同的过滤,返回完整的2个数。
请注意,如果您更改代码示例,使其如下所示:
String[] colors = {"green", "red", "blue", "brown"};
string s = "e";
var query = colors.Where(c => c.Contains(s)).ToArray();
s = "n";
query = query.Where(c=> c.Contains("n"));
Console.WriteLine(query.Count());
...它会产生你期望的结果。以上调用ToArray()
强制使用当前s
的当前值来评估枚举。稍后对变量的更改将不会影响最终结果。
答案 1 :(得分:1)
在第二个片段中,您将围绕变量s创建一个闭包。在使用.Count()枚举列表之前,不会计算where子句中的表达式。那时,s已经被重新分配,所以你的查询实际上是colors.Where(c =&gt; c.Contains(“n”))。where(c =&gt; c.Contains(“n”))
答案 2 :(得分:1)
原因是LINQ查询基于“延迟执行” - 查询已构建但未真正执行,除非您专门遍历集合(例如foreach循环)或调用.ToList()或.ToArray()扩展名方法
答案 3 :(得分:1)
当where
调用发生时,您的Count()
逻辑将实际执行。因此,在Console.WriteLine
之前,你的lambda表达式甚至没有执行过一次(导致你的字符串变量稍后在lambdas中计算为n
,因为已经执行了赋值 - 你可以检查一下用你的调试器)。这个概念称为Deferred Execution,在查询数据库时非常有用(因为在整个查询已知的情况下编译SQL并对数据库运行)。
答案 4 :(得分:1)
是的,你是对的,因为s = "n";
Linq懒得评价。在您的示例中,第一个where
未直接评估。只有在需要时(迭代)。因为lambda捕获了s
变量,所以当前值甚至用于第一个lambda。
在第二个ToList()
之前尝试where
以强制评估第一个IEnumerable。
String[] colors = { "green", "red", "blue", "brown" };
string s = "e";
var query = colors.Where(c => c.Contains(s)).ToList();
s = "n";
query = query.Where(c => c.Contains("n"));