修改 ReSharper 中的关闭警告

Posted

技术标签:

【中文标题】修改 ReSharper 中的关闭警告【英文标题】:modified closure warning in ReSharper 【发布时间】:2011-02-26 10:04:57 【问题描述】:

我希望有人能向我解释这段代码中可能发生的坏事,这会导致 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:这不是位运算符吗? ...或者这与bool 无关吗? ChaosPandion 表示result &&= ContainsKey(key); this 和其他几个的副本。 @adrianbanks:我可能应该搜索“修改后的关闭”;当我在提交表单中输入问题的标题时,我只是没有看到任何相同的问题。 【参考方案1】:

http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/

关于这个问题的广泛讨论,以及我们在假设的 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()));

这可能是一个很容易犯的错误。我听人说过,如果你真正了解闭包/函数式编程/等。你不应该犯这个错误。我也看到过确实对这些概念有很好理解的专业开发人员犯了这个确切的错误。

【讨论】:

【参考方案3】:

当您修改 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);

【讨论】:

所以我给result 一个不同的值的那一行是导致警告的原因,因为result 也在TakeWhile 的lambda 中使用? 我认为您的foreach 替换是错误的:如果所有keys 都在字典中(由ContainsKey 单独决定),则该方法应返回true。但是,return keys.All(ContainsKey); 有效!【参考方案4】:

在那个特定的代码中什么都没有,以以下为例:

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 警告您它可能会被修改,并且延迟执行可能会给您带来问题。

【讨论】:

【参考方案5】:

嗯,警告是因为result 可以在执行闭包之前更改(变量是在执行时获取的,而不是定义)。就您而言,您实际上是在利用这一事实。如果你使用 resharper reccomodation,它实际上会破坏你的代码,因为key =&gt; result1 总是返回 true。

【讨论】:

以上是关于修改 ReSharper 中的关闭警告的主要内容,如果未能解决你的问题,请参考以下文章

在 Visual Studio 2010 中查看 SQL 文件时 ReSharper 无法工作的任何原因?

ReSharper C++ 中的配色方案混乱

来自 ReSharper 的“从不使用自动属性访问器”警告

如何修复:在闭包 resharper 警告中访问 foreach 变量?

ReSharper 警告:“泛型类型的静态字段”

C++ 的 ReSharper [关闭]