警告:在lambda表达式中使用迭代变量可能会导致意想不到的结果。

3

编辑:这是一个更简单的例子来说明这个问题(我已经删除了原始问题):

Dim numbers1 As New List(Of Int32)({1, 2, 3})
Dim numbers2 As New List(Of Int32)({3, 4, 5})
For Each n1 In numbers1
    ' no warning '
    Dim contains = numbers2.Contains(n1)
Next
For Each n1 In numbers1
    ' warning on n1'
    Dim contains = (From num In numbers2 Where num = n1).Any
Next

所以我仍然不明白为什么编译器认为我在第二次迭代中可能会得到意外的结果,而我在第一次迭代中是安全的。我不认为@ee-m的有趣的链接提供了这种行为的原因(这不是一个for-each问题,For n1 As Int32 = 1 To 3也会导致编译器警告)。
我并不真正相信以下应该是“最佳实践”:
For Each n1 In numbers1
    Dim number1 = n1
    ' no warning'
    Dim contains = (From num In numbers2 Where num = number1).Any
Next

本地变量number1是多余的,正如@Meta-Knight已经强调的那样,它使代码不易读。注意:这三种方法都是安全的,并且可以得到正确的结果。

2个回答

4

Eric Lippert在这个主题上写了几篇博客文章(代码示例是用C#而不是VB),介绍了一些可能会出现的“陷阱”,你可能会觉得有趣:

闭合循环变量被认为是有害的


1
确实很有趣。在C#中没有这样的警告。用Lippert的话来说:“一个警告,警告正确行为是非常糟糕的警告”。 - Meta-Knight
+1 有趣的链接。也许我误解了Eric的观点,但即使我用For n1 As Int32 = 1 To 3替换for-each循环,问题仍然存在。那么变量的作用域不应该是循环体内吗 - Tim Schmelter
@Tim Schmelter:简单的for循环也会出现完全相同的问题。请记住,C#和VB之间存在差异。在VB中,您无论如何都不能在循环外引用循环变量。但这并不改变同一变量从一个迭代到下一个被重复使用的事实。 - Meta-Knight

3
正如消息所说,它可能会产生不良影响。在您的情况下,.ToList() 使其安全,但这对编译器来说很难验证。
我建议采用将其复制到本地变量(Dim exc = excel)作为标准的“最佳实践”。

为什么编译器难以验证?我理解LINQ查询无法知道它将立即执行,因为ToList已经超出了它的上下文。但是迭代变量不能更改,因为此时它已经执行。我认为编译器知道这一点,所以他可以将迭代变量“硬分配”到查询中。但是我采纳了您的建议,将其复制到本地变量中。 - Tim Schmelter
1
这个问题在于“Dim exc = excel”这一行是完全多余的,使得代码不够清晰,因此我不知道我们是否真的能够称之为“最佳实践”。我正在考虑自己抑制这个警告消息,因为像这种情况有很多误报。 - Meta-Knight
@meta- 多余的:是的,不太清晰:不一定。只需选择一个好名称即可。这是关于提前思考,如果更改为Parallel.ForEach,您真的会需要它。 - H H
很难为指向与循环变量完全相同的对象的变量找到一个好的名称。因此,您可以使用缩写,这会使代码不够清晰,或者添加一个多余的形容词,例如“currentExcel”或“excel2”,这也可能会令人困惑。您有关于命名这种变量的任何提示吗? - Meta-Knight
@Meta-Knight,@Henk Holterman: 在这种情况下,我将第一个本地变量命名为 Dim idExcel = excel.idReport,因为后面我只使用了这个整数而不是整个对象。它仍然是多余和啰嗦的,但并不是不清晰。我不确定编译器团队修复这个问题有多么复杂,但他们可能有其他优先事项。 - Tim Schmelter
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接