ReSharper中的修改闭包警告

4
我希望有人能够向我解释下面的代码中可能会发生什么问题,导致ReSharper出现“访问已修改闭包”的警告:
bool result = true;

foreach (string key in keys.TakeWhile(key => result))
{
    result = result && ContainsKey(key);
}

return result;

即使上面的代码看起来很安全,但在其他“修改闭包”实例中可能会发生什么不好的事情?我经常在使用LINQ查询时看到这个警告,但我倾向于忽略它,因为我不知道可能出现什么问题。ReSharper尝试通过创建第二个变量来解决这个问题,但我觉得这似乎是毫无意义的,例如它将上面的foreach行更改为:
bool result1 = result;
foreach (string key in keys.TakeWhile(key => result1))

更新:另外一件事,显然整个代码块可以被转换为以下语句,这将不会引起任何修改闭包警告:

return keys.Aggregate(
    true,
    (current, key) => current && ContainsKey(key)
);

如果你愿意的话,你可以将你的代码缩短为 result &= ContainsKey(key); - ChaosPandion
@ChaosPandion:那不是位运算符吗?或者说对于bool类型没有关系吗? - Sarah Vessels
1
ChaosPandion 意味着 result &&= ContainsKey(key); - SLaks
重复的问题,与此(https://dev59.com/iXI-5IYBdhLWcg3wwbZM)和其他几个类似。 - adrianbanks
@adrianbanks:我可能应该搜索“修改闭包”;当我在提交表单中输入标题时,我没有看到任何相同的问题。 - Sarah Vessels
5个回答

8
在这段特定的代码中,没有什么问题。以以下内容为例:
int myVal = 2;

var results = myDatabase.Table.Where(record => record.Value == myVal);

myVal = 3;

foreach( var myResult in results )
{
    // TODO: stuff
}

看起来结果将返回所有值为2的记录,因为在声明查询时myVal被设置为2。然而,由于延迟执行,实际上它将返回所有值为3的记录,因为查询直到迭代才被执行。

在您的示例中,该值未被修改,但Resharper警告您可能会修改它,并且延迟执行可能会导致问题。


6
当您修改result变量时,闭包(在lambda表达式中使用变量)将捕获这种变化,这常常会让不完全理解闭包的程序员感到意外,因此Resharper对此发出了警告。
通过创建一个单独的result1变量,只在lambda表达式中使用,它将忽略原始result变量的任何后续更改。
在您的代码中,您依赖于闭包捕获对原始变量的更改,以便知道何时停止。
顺便说一下,没有使用LINQ编写函数的最简单方法如下:
foreach (string key in keys) {
    if (ContainsKey(key))
        return true;   
}
return false;

利用LINQ,你可以简单地调用Any()

return keys.Any<string>(ContainsKey);

因此,赋予 result 不同值的那一行引起了警告,因为 result 也在 TakeWhile 中的 lambda 表达式中使用了? - Sarah Vessels
我认为你的 foreach 替换是错误的:该方法应当在所有 keys 都存在于字典中时返回 true(由 ContainsKey 分别决定)。然而,return keys.All(ContainsKey); 是可行的! - Sarah Vessels

3
请看这里,进行了广泛的讨论此问题以及我们在假设的将来版本的C#中可能采取的措施。

2

我不确定ReSharper是否会对此发出完全相同的警告,但以下内容说明了类似的情况。循环的迭代器用于LINQ子句,但是该子句实际上直到循环结束后才被评估,此时迭代器变量已更改。以下是一个人为制造的示例,看起来应该打印从1到100的所有奇数,但实际上打印的是从1到99的所有数字。

var notEven = Enumerable.Range(1,100);
foreach (int i in Enumerable.Range(1, 50))
{
    notEven = notEven.Where(s => s != i * 2);
}

Console.WriteLine(notEven.Count());
Console.WriteLine(string.Join(", ", notEven.Select(s => s.ToString()).ToArray()));

这是一个非常容易犯的错误。我听过人们说,如果你真正理解闭包/函数式编程等概念,就不应该犯这个错误。但我也见过确实掌握这些概念的专业开发人员犯下这个错误。


0

好的,警告是因为在闭包执行之前可能会更改result(变量在执行时被取出,而不是定义)。在您的情况下,您实际上正在利用这个事实。如果您使用resharper建议,它实际上会破坏您的代码,因为key => result1始终返回true。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接