【问题标题】:Why don't all of these variables get treated the same way?为什么不是所有这些变量都得到同样的处理?
【发布时间】:2012-11-07 09:29:05
【问题描述】:

我正在检查 VB.NET 中变量声明的位置是否无关紧要,除了范围(this question),我想我最好检查一下当它们被“提升”到闭包中时会发生什么。我没有阅读规范,但我无法解释这些结果:

Dim outer As Integer
For i = 1 To 2
 Dim inner As Integer
 Try
  Dim inner2 As Integer
  Do
   Dim inner3 As Integer
   Call Sub()
    Dim inner4 As Integer
    Console.WriteLine(outer & ", " & inner & ", " & inner2 & ", " & inner3 & ", " & inner4)
    outer = i
    inner = i
    inner2 = i
    inner3 = i
    inner4 = i
   End Sub()
  Loop Until True
 Finally
 End Try
Next

以上输出:

0, 0, 0, 0, 0
1, 1, 0, 1, 0

inner4 每次都被重置是有意义的,其他innerX 全部或不重置,但为什么只有inner2?!

【问题讨论】:

  • Call Sub() 究竟做了什么?
  • @AhmadAl-Mutawa 足够公平的问题。扩展版是Dim closure = Sub()...End Sub : closure(),我已经测试过了,结果是一样的。
  • 我认为inner3inner2 更有趣。我认为应该是 0。
  • @Damien_The_Unbeliever 是的,鉴于inner2 已重置,我绝对不明白为什么inner3 没有。显然Try 是“特殊的”,这就是innerinner2 之间存在差异的原因。我还在For 循环之外测试了Try,并且在那里声明的变量具有与outer 相同的行为。

标签: vb.net closures


【解决方案1】:

来自 MSDN(重点是我的):

[...]编译器基本上做了什么,当它进入一个包含提升变量的新范围时,编译器将检查是否已经存在闭包实例;如果是这样,编译器将创建一个新的闭包实例并从先前的闭包中重置变量的值。

请注意,编译器仅在生成闭包的函数中检测到循环或 GoTo 时才执行上述检查

Link

【讨论】:

  • 正如我在此代码的简单(非封闭)版本来源的答案中所说,scope 是声明所在的块,但 lifetime 是整个例行公事。您对关闭中发生的事情有任何参考吗?
  • @MarkHurd - 很抱歉含糊其辞,这是我无法很好地解析为答案的旧记忆。我为您找到了参考。
  • +1 到目前为止,您已经确定了两件事:VB 在这里做了一些“特别”的事情,在 Reflector 中查看这段代码可能会确认发生了什么。
  • @MarkHurd - 是的,反射器会回答它。我认为在使用匿名方法时保留类似非闭包的行为似乎是一种微弱的尝试。当在循环中输入任何新的作用域块时,编译器可能应该这样做,而不仅仅是循环块。它可能会受到递归问题的限制?
【解决方案2】:

(这更像是一条注释,但需要太多代码才能保持原样。)

Reflector 确实显示了发生了什么

<STAThread> _
Public Shared Sub Main()
Dim e$__ As New _Closure$__1
Try 
    Dim e$__2 As New _Closure$__2
    Dim e$__3 As New _Closure$__3
    e$__3.$VB$Local_i = 1
    Do
        Dim e$__4 As _Closure$__4
        e$__4 = New _Closure$__4(e$__4)
        Try 
            Dim e$__5 As New _Closure$__5
            Do
                Dim e$__6 As _Closure$__6
                e$__6 = New _Closure$__6(e$__6)
                e$__6.$VB$NonLocal_$VB$Closure_ClosureVariable_8_5 = e$__5
                e$__6.$VB$NonLocal_$VB$Closure_ClosureVariable_6_4 = e$__4
                e$__6.$VB$NonLocal_$VB$Closure_ClosureVariable_6_6 = e$__3
                e$__6.$VB$NonLocal_$VB$Closure_ClosureVariable_4_4 = e$__2
                e$__6.$VB$NonLocal_$VB$Closure_ClosureVariable_2_B = e$__
                Dim e_ As VB$AnonymousDelegate_0 = New VB$AnonymousDelegate_0(AddressOf e$__6._Lambda$__1)
                e_.Invoke
            Loop While (0 <> 0)
        End Try
        e$__3.$VB$Local_i += 1
    Loop While (e$__3.$VB$Local_i <= 2)
End Try
End Sub

(这是基于我的代码,其中在For 之外包含一个Try。)

您可以在这里看到For 循环(被视为Do 循环,之前设置了$VB$Local_i)和内部Do 生成的闭包确实具有传入的闭包的前一个实例,但是Try 没有得到那种待遇。

还是不知道为什么?对我来说似乎是一个错误。如果我在一天左右没有得到合理的“借口”(:-)),我会把它放在 Connect 上。 (有人可以确认 .NET 4.5 VB11 的性能相同吗?)

【讨论】:

  • 通过 LinqPad 5 beta,这已在 VB.NET 14 中修复:第二行现在是 1, 1, 1, 1, 0
  • 只是一个想法!!因为在 try 块中声明的变量在其外部是不可见的,并且系统处理异常的方式使得 call sub 看起来像是在块外部。因此变量 inner2 不是调用子块中的变量。尝试使用 call sub(var) 并将 inner2 作为参数传递给 call sub。
  • @milevyo 我确信有解决方法,但后来的版本“修复”这表明它应该一直都是这样。
猜你喜欢
  • 1970-01-01
  • 2021-08-25
  • 1970-01-01
  • 1970-01-01
  • 2020-07-10
  • 1970-01-01
  • 2016-11-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多