首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么在lambda表达式中使用迭代变量是不好的

为什么在lambda表达式中使用迭代变量是不好的
EN

Stack Overflow用户
提问于 2008-10-22 22:47:52
回答 3查看 15.1K关注 0票数 54

表达式中使用迭代变量可能会产生意外的结果。

但是我想知道为什么在lambda中使用迭代变量是个坏主意?

我以后会引起什么问题呢?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2008-10-22 22:52:39

考虑下面的代码:

代码语言:javascript
复制
List<Action> actions = new List<Action>();

for (int i = 0; i < 10; i++)
{
    actions.Add(() => Console.WriteLine(i));
}

foreach (Action action in actions)
{
    action();
}

您希望打印的内容是什么?显而易见的答案是0...9 --但实际上它会打印10,10次。这是因为只有一个变量被所有的委托所捕获。这种行为是意想不到的。

编辑:我刚刚看到你谈论的是VB.NET而不是C#。我相信VB.NET有更复杂的规则,这是由于变量在迭代中保持其值的方式。This post by Jared Parsons给出了一些关于涉及到的困难的信息-尽管它是从2007年开始的,所以实际的行为可能从那时起就发生了变化。

票数 55
EN

Stack Overflow用户

发布于 2008-10-22 22:51:46

假设你这里指的是C#。

这是因为编译器实现闭包的方式。使用迭代变量可能会导致访问修改后的闭包时出现问题(请注意,我说的“能”而不是“将”会导致问题,因为有时它不会发生,这取决于方法中的其他内容,而有时您实际上想要访问修改后的闭包)。

更多信息:

http://blogs.msdn.com/abhinaba/archive/2005/10/18/482180.aspx

更多信息:

http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx

http://blogs.msdn.com/oldnewthing/archive/2006/08/03/687529.aspx

http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx

票数 8
EN

Stack Overflow用户

发布于 2019-03-24 02:48:38

.NET中的闭包理论

Local variables: scope vs. lifetime (plus closures) (2010年存档)

(强调我的)

在这种情况下,我们使用了一个闭包。,

。闭包只是一个特殊的结构,它存在于方法之外,它包含了其他方法需要引用的局部变量。当查询引用局部变量(或参数)时,闭包将捕获该变量,并且对该变量的所有引用都将重定向到闭包。

当您考虑闭包在.NET中的工作方式时,我建议您牢记以下要点,这是设计人员在实现此功能时必须处理的内容:

  • 注意,“变量捕获”和λ表达式不是IL特性,VB.NET (和C#)必须使用现有工具来实现这些特性,在本例中是类和lambda。换句话说,局部变量不能真正持久化到它们的作用域之外。语言所做的就是让它看起来可以,但它不是一个完美的abstraction.
  • Func(Of T) (即,Delegate)实例没有办法存储传递到them.
  • Though,的参数,而是存储方法所在的类的实例。这是传递给lambda expressions.

的.NET框架用来“记住”参数的途径

好吧,让我们来看看吧!

示例代码:

假设你写了一些这样的代码:

代码语言:javascript
复制
' Prints 4,4,4,4
Sub VBDotNetSample()
    Dim funcList As New List(Of Func(Of Integer))

    For indexParameter As Integer = 0 To 3
        'The compiler says:
        '   Warning     BC42324 Using the iteration variable in a lambda expression may have unexpected results.  
        '   Instead, create a local variable within the loop and assign it the value of the iteration variable

        funcList.Add(Function()indexParameter)

    Next

    
    For Each lambdaFunc As Func(Of Integer) In funcList
        Console.Write($"{lambdaFunc()}")

    Next

End Sub

您可能希望代码打印0,1,2,3,但实际上它打印的是4,4,4,4,这是因为indexParameterSub VBDotNetSample()的作用域中被“捕获”了,而不是在For循环作用域中。

反编译的示例代码

就我个人而言,我真的很想看看编译器为此生成了什么样的代码,所以我继续使用JetBrains DotPeek。我将编译器生成的代码手工翻译回VB.NET。

我的注释和变量名。代码以不影响代码行为的方式进行了略微的简化。

代码语言:javascript
复制
Module Decompiledcode
    ' Prints 4,4,4,4
    Sub CompilerGenerated()

        Dim funcList As New List(Of Func(Of Integer))
        
        '***********************************************************************************************
        ' There's only one instance of the closureHelperClass for the entire Sub
        ' That means that all the iterations of the for loop below are referencing
        ' the same class instance; that means that it can't remember the value of Local_indexParameter
        ' at each iteration, and it only remembers the last one (4).
        '***********************************************************************************************
        Dim closureHelperClass As New ClosureHelperClass_CompilerGenerated

        For closureHelperClass.Local_indexParameter = 0 To 3

            ' NOTE that it refers to the Lambda *instance* method of the ClosureHelperClass_CompilerGenerated class, 
            ' Remember that delegates implicitly carry the instance of the class in their Target 
            ' property, it's not just referring to the Lambda method, it's referring to the Lambda
            ' method on the closureHelperClass instance of the class!
            Dim closureHelperClassMethodFunc As Func(Of Integer) = AddressOf closureHelperClass.Lambda
            funcList.Add(closureHelperClassMethodFunc)
        
        Next
        'closureHelperClass.Local_indexParameter is 4 now.

        'Run each stored lambda expression (on the Delegate's Target, closureHelperClass)
        For Each lambdaFunc As Func(Of Integer) in funcList      
            
            'The return value will always be 4, because it's just returning closureHelperClass.Local_indexParameter.
            Dim retVal_AlwaysFour As Integer = lambdaFunc()

            Console.Write($"{retVal_AlwaysFour}")

        Next

    End Sub

    Friend NotInheritable Class ClosureHelperClass_CompilerGenerated
        ' Yes the compiler really does generate a class with public fields.
        Public Local_indexParameter As Integer

        'The body of your lambda expression goes here, note that this method
        'takes no parameters and uses a field of this class (the stored parameter value) instead.
        Friend Function Lambda() As Integer
            Return Me.Local_indexParameter

        End Function

    End Class

End Module

注意,整个Sub CompilerGenerated只有一个closureHelperClass实例,所以函数无法打印中间的For循环索引值0、1、2、3(没有地方存储这些值)。代码只打印4,即最终索引值(在For循环之后)四次。

脚注:

  • 在这篇文章中暗示了“从.NET 4.6.1开始”,但在我看来,这些限制不太可能发生重大变化;如果你发现一个无法重现这些结果的设置,请给我留言。

“但是jrh,你为什么这么晚才回复呢?”

这篇文章中链接的页面要么丢失,要么在shambles.

  • There中没有vb.net标签问题的答案,截至撰写本文时,有一个C# (错误的语言)答案和一个主要只有链接的答案(有3个无效链接)。
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/227820

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档