我希望有人可以向我解释这段代码会发生什么不好的事情,这会导致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)
);
答案 0 :(得分:8)
在该特定代码中没有,请以下面的例子为例:
int myVal = 2;
var results = myDatabase.Table.Where(record => record.Value == myVal);
myVal = 3;
foreach( var myResult in results )
{
// TODO: stuff
}
看起来结果将返回值为2
的所有记录,因为这是myVal在您声明查询时设置的内容。但是,由于延迟执行,它实际上将是值为3
的所有记录,因为在迭代之前不会执行查询。
在您的示例中,该值未被修改,但Resharper警告您它可能是,并且延迟执行可能会导致您出现问题。
答案 1 :(得分:6)
修改result
变量时,闭包(在lambda表达式中使用变量)将获取更改。
对于那些不完全理解闭包的程序员来说,这经常是一个意想不到的惊喜,因此Resharper会对此发出警告。
通过创建一个仅在lambda表达式中使用的单独result1
变量,它将忽略对原始result
变量的任何后续更改。
在你的代码中,你依靠闭包来获取对原始变量的更改,以便它知道何时停止。
顺便说一句,没有LINQ编写函数的最简单方法是这样的:
foreach (string key in keys) {
if (ContainsKey(key))
return true;
}
return false;
使用LINQ,您只需拨打Any()
:
return keys.Any<string>(ContainsKey);
答案 2 :(得分:3)
见
http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/
有关此问题的广泛讨论以及我们可能会在假设的未来版本的C#中做些什么来缓解它。
答案 3 :(得分: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()));
这可能是一个很容易犯的错误。我听说有人说如果你真的了解闭包/函数式编程/等等。你永远不应该犯这个错误。我也见过那些确实很好地掌握这些概念的专业开发人员犯了这个错误。
答案 4 :(得分:0)
嗯,警告是因为result
可以在执行闭包之前更改(变量是在执行时获取的,而不是定义)。在您的情况下,您实际上正在利用这一事实。如果你使用resharper reccomodation,它实际上会破坏你的代码,因为key => result1
总是返回true。