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

时间:2011-12-27 21:12:40

标签: c# warnings resharper-6.0 list-comprehension

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

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

我认为我已基本了解此警告试图保护我:dr在GetTheDataTableStrings的输出被询问之前多次更改,因此调用者可能无法获得我期望的输出/行为。 / p>

但是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();
}

2 个答案:

答案 0 :(得分:21)

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

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

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

“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不会关闭任何外部变量,因此不会意外修改变量。

答案 1 :(得分: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语言规范.docx
  • C:\ Program Files(x86)\ Microsoft Visual Studio 11.0 \ VC#\ Specifications \ 1033 \ CSharp Language Specification.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#在while循环之外声明了v。)

Microsoft Visual Basic语言规范版本11.0

10.9.3 For Each ... Next Statements(Annotation)

  

语言版本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”。