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

发布于 2024-12-22 20:39:17 字数 872 浏览 3 评论 0原文

ReSharper 6.0 针对第一个代码片段中的 dr 标识符向我发出“访问修改后的闭包”警告。

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

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

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

ReSharper 6.0 gives me the "Access to modified closure" warning for the dr identifier in the first code snippet.

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

I think I have a basic understanding of what this warning is trying to protect me from: dr changes several times before GetTheDataTableStrings's output is interrogated, and so the caller might not get the output/behavior I expect.

But R# doesn't give me any warning for the second code snippet.

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

Is it safe for me to discard this warning/concern when using the comprehension syntax?

Other code:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

迷爱 2024-12-29 20:39:17

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

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

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

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

“for”循环不会改变。

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

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

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

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

First off, you are correct to be concerned about the first version. Each delegate created by that lambda is closed over the same variable and therefore as that variable changes, the meaning of the query changes.

Second, FYI we are highly likely to fix this in the next version of C#; this is a major pain point for developers.

(UPDATE: This answer was written in 2011. We did in fact take the fix described below in C# 5.)

In the next version each time you run through the "foreach" loop we will generate a new loop variable rather than closing over the same variable every time. This is a "breaking" change but in the vast majority of cases the "break" will be fixing rather than causing bugs.

The "for" loop will not be changed.

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

Third, there is no problem with the query comprehension version because there is no closed-over variable that is being modified. The query comprehension form is the same as if you'd said:

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

The lambda is not closed over any outer variable, so there is no variable to be modified accidentally.

撩动你心 2024-12-29 20:39:17

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 语句

v 在 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 语句(注释)

该语言版本 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”,并且所有三个 lambda 表达式都捕获相同的“x”,并且当执行 lambda 表达式时,它的值是数字 3。
从 Visual Basic 11.0 开始,它打印“1, 2, 3”。这是因为每个 lambda 捕获不同的变量“x”。

The issue that Resharper is warning about has been resolved in both C# 5.0 and VB.Net 11.0. The following are extracts from the language specifications. Note that the specifications can be found in the following paths by default on a machine with Visual Studio 2012 installed.

  • 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# Language Specification Version 5.0

8.8.4 The foreach statement

The placement of v inside the while loop is important for how it is captured by any anonymous function occurring in the embedded-statement.

For example:

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

If v was declared outside of the while loop, it would be shared among all iterations, and its value after the for loop would be the final value, 13, which is what the invocation of f would print. Instead, because each iteration has its own variable v, the one captured by f in the first iteration will continue to hold the value 7, which is what will be printed. (Note: earlier versions of C# declared v outside of the while loop.)

The Microsoft Visual Basic Language Specification Version 11.0

10.9.3 For Each...Next Statements (Annotation)

There is a slight change in behavior between version 10.0 and 11.0 of the language. Prior to 11.0, a fresh iteration variable was not created for each iteration of the loop. This difference is observable only if the iteration variable is captured by a lambda or a LINQ expression which is then invoked after the loop.

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()

Up to Visual Basic 10.0, this produced a warning at compile-time and printed "3" three times. That was because there was only a single variable "x" shared by all iterations of the loop, and all three lambdas captured the same "x", and by the time the lambdas were executed it then held the number 3.
As of Visual Basic 11.0, it prints "1, 2, 3". That is because each lambda captures a different variable "x".

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文