10

drReSharper 6.0 为第一个代码片段中的标识符提供了“访问修改的闭包”警告。

private IEnumerable<string> GetTheDataTableStrings(DataTable dt) {
    foreach (DataRow dr in dt.Rows) {
        yield return GetStringFuncOutput(() => dr.ToString());
    }
}

我想我对这个警告试图保护我免受什么有一个基本的了解:dr在 GetTheDataTableStrings 的输出被询问之前发生了多次更改,因此调用者可能无法获得我期望的输出/行为。

但是 R# 没有给我第二个代码片段的任何警告。

private IEnumerable<string> GetTheDataTableStrings(DataTable dt) {
    return from DataRow dr in dt.Rows select GetStringFuncOutput(dr.ToString);
}

在使用理解语法时,我可以安全地放弃这个警告/关注吗?

其他代码:

string GetStringFuncOutput(Func<string> stringFunc) {
    return stringFunc();
}
4

2 回答 2

22

首先,您对第一个版本的关注是正确的。由该 lambda 创建的每个委托都在同一个变量上关闭,因此随着该变量的变化,查询的含义也会发生变化。

其次,仅供参考,我们很可能会在下一版本的 C# 中修复此问题;这是开发人员的主要痛点。

(更新:这个答案写于 2011 年。事实上,我们确实采用了 C# 5 中描述的修复方法。)

在下一个版本中,每次运行“foreach”循环时,我们都会生成一个的循环变量,而不是每次都关闭同一个变量。这是一个“破坏性”更改,但在绝大多数情况下,“破坏”将是修复而不是导致错误。

“for”循环不会改变。

有关详细信息,请参阅http://ericlippert.com/2009/11/12/closure-over-the-loop-variable-considered-harmful-part-one/

第三,查询理解版本没有问题,因为没有被修改的封闭变量。查询理解形式与您所说的相同:

return dt.Rows.Select(dr=>GetStringFuncOutput(dr.ToString));

lambda 不会关闭任何外部变量,因此不会意外修改变量。

于 2011-12-27T21:23:08.363 回答
5

Resharper 警告的问题已在 C# 5.0 和 VB.Net 11.0 中得到解决。以下是语言规范的摘录。请注意,默认情况下,可以在安装了 Visual Studio 2012 的计算机上的以下路径中找到规范。

  • C:\Program Files (x86)\Microsoft Visual Studio 11.0\VB\Specifications\1033\Visual Basic Language Specification.docx
  • C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC#\Specifications\1033\CSharp Language Specification.docx

C# 语言规范版本 5.0

8.8.4 foreach 语句

v 在 while 循环中的位置对于嵌入语句中出现的任何匿名函数如何捕获它很重要。

例如:

int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();

如果 v 在 while 循环之外声明,它将在所有迭代之间共享,并且它在 for 循环之后的值将是最终值 13,这就是 f 调用将打印的值。相反,因为每次迭代都有自己的变量 v,所以在第一次迭代中被 f 捕获的变量将继续保持值 7,这就是将要打印的值。(注意:早期版本的 C# 在 while 循环之外声明了 v。)

Microsoft Visual Basic 语言规范版本 11.0

10.9.3 For Each...Next 语句(注解)

该语言的 10.0 和 11.0 版本之间的行为略有不同。在 11.0 之前,不会为循环的每次迭代创建新的迭代变量。仅当迭代变量由 lambda 或 LINQ 表达式捕获时才能观察到这种差异,然后在循环之后调用该表达式。

Dim lambdas As New List(Of Action)
For Each x In {1,2,3}
   lambdas.Add(Sub() Console.WriteLine(x)
Next
lambdas(0).Invoke()
lambdas(1).Invoke()
lambdas(2).Invoke()

在 Visual Basic 10.0 之前,这会在编译时产生警告并打印 3 次“3”。这是因为只有一个变量“x”被循环的所有迭代共享,并且所有三个 lambdas 都捕获了相同的“x”,并且当 lambdas 被执行时,它就保持了数字 3。从 Visual Basic 开始11.0,它打印“1、2、3”。那是因为每个 lambda 捕获一个不同的变量“x”。

于 2012-09-18T10:02:26.230 回答