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();
}
答案 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#语言规范版本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”。