是否通过理解语法解决了“访问修改后的闭包”的问题?

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

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

我想我对这个警告试图保护我的基本理解是:在调查GetTheDataTableStrings的输出之前, dr会多次更改,因此调用者可能无法获得我期望的输出/行为。

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

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

使用理解语法时,放弃此警告/关注是否安全?

其他代码:

 string GetStringFuncOutput(Func stringFunc) { return stringFunc(); } 

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

第二,仅供参考我们很有可能在下一版C#中解决这个问题; 这对开发人员来说是一个重大的痛点。

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

“for”循环不会改变。

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

第三,查询理解版本没有问题,因为没有被修改的关闭变量。 查询理解表与您说的一样:

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

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

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语言规范.docx
  • C:\ Program Files(x86)\ Microsoft Visual Studio 11.0 \ VC#\ Specifications \ 1033 \ CSharp语言规范.docx

C#语言规范版本5.0

8.8.4 foreach声明

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

例如:

 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#声明v在while循环之外。)

Microsoft Visual Basic语言规范版本11.0

10.9.3 For Each …下一个语句(注释)

版本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”三次。 那是因为循环的所有迭代只共享一个变量“x”,并且所有三个lambdas都捕获了相同的“x”,并且当lambdas被执行时它然后保持数字3.从Visual Basic开始11.0,打印“1,2,3”。 那是因为每个lambda捕获一个不同的变量“x”。