Dot-Net

VB.NET 迭代器函式失去局部變數

  • February 24, 2016

幾個 showstoppers延遲遷移到 .NET 4.6 執行時之後,我終於對遷移到 C#6/VB14 編譯器感到滿意,直到我遇到了一個關鍵問題,即 VB.NET 中的迭代器函式丟棄了局部變數。

以下程式碼範例在 Visual Studio 2015 / msbuild 中以發布模式(優化)編譯時,將在註釋行上引發空引用異常。

Module Module1

   Sub Main()
       For Each o As Integer In GetAllStuff()
           Console.WriteLine(o.ToString())
       Next

       Console.ReadKey()

   End Sub

   Private Iterator Function GetAllStuff() As IEnumerable(Of Integer)
       Dim map As Dictionary(Of String, String) = New Dictionary(Of String, String)
       Dim tasks As New List(Of Integer)
       tasks.Add(1)

       For Each task As Integer In tasks
           Yield task
       Next

       'The value of map becomes null here under the new VB14 compiler in Release on .NET 4.6'
       For Each s As String In map.Values
           Yield 100
       Next
   End Function

End Module

所以,這是相當可怕的。

值得注意的是,此程式碼的 C# 等效項可以毫無問題地執行。更重要的是,這在以前版本的 VB 編譯器下有效(並且已經有效)。比較兩個不同編譯器創建的狀態機之間的 MSIL,新編譯器似乎幾乎只使用 .locals 來儲存局部變數,而舊編譯器使用狀態機上的可變欄位來保存局部值。

我錯過了什麼嗎?我無法在 VB 中找到任何關於迭代器的重大更改的文件(我也無法想像會是這種情況),但也沒有發現其他任何人遇到過這個問題。

這個特殊的例子可以通過map在第一個 foreach 循環之後移動 to 的構造來解決,但是我擔心的是我對這個問題的真正意義沒有任何感覺。我對修改程式碼以“讓它工作”不感興趣。在我們廣泛的程式碼庫中,我還能在哪裡遇到同樣的問題?我已經在Connect上送出了這個問題,但這通常感覺像是一個黑洞。

更新

有人剛剛在 Roslyn GitHub 頁面上報告了與非同步狀態機相同的問題: https ://github.com/dotnet/roslyn/issues/9001

希望這開始得到一點關注。

首先,感謝您關注我向 Roslyn 團隊提出的問題。

我從https://github.com/dotnet/roslyn(master分支)中提取了最新的 Roslyn 原始碼,並向 BasicCompilerEmitTest 項目添加了一個額外的單元測試,如下所示:

Imports Microsoft.CodeAnalysis.VisualBasic.UnitTests

Public Class KirillsTests
 Inherits BasicTestBase

 <Fact>
 Public Sub IteratorVariableCaptureTest()
   Dim source =
<compilation name="Iterators">
 <file name="a.vb">
Imports System
Imports System.Collections.Generic

Module Module1

   Sub Main()
       For Each o As Integer In GetAllStuff()
           Console.WriteLine(o.ToString())
       Next

       Console.WriteLine("done")
   End Sub

   Private Iterator Function GetAllStuff() As IEnumerable(Of Integer)
       Dim map As Dictionary(Of String, String) = New Dictionary(Of String, String)
       Dim tasks As New List(Of Integer)
       tasks.Add(1)

       For Each task As Integer In tasks
           Yield task
       Next

       'The value of map becomes null here under the new VB14 compiler in Release on .NET 4.6'
       For Each s As String In map.Values
           Yield 100
       Next
   End Function

End Module
 </file>
</compilation>

   Dim expectedOutput = <![CDATA[1
done]]>

   Dim compilation = CompilationUtils.CreateCompilationWithReferences(source, references:=LatestVbReferences, options:=TestOptions.DebugExe)
   CompileAndVerify(compilation, expectedOutput:=expectedOutput)
   CompileAndVerify(compilation.WithOptions(TestOptions.ReleaseExe), expectedOutput:=expectedOutput)
 End Sub

End Class

由於XElementXCData使用,這可能看起來像一個令人費解的混亂,但這是其他 Roslyn 單元測試使用的格式。

我只對您在問題中發布的程式碼進行了一項更改 - 替換Console.ReadKey()Console.WriteLine("done")以便我可以跟踪成功完成(因為CompileAndVerify只是忽略異常)。

上述測試通過。沒有NullReferenceException訪問map.Values權限,輸出為:

1
完畢

……正如預期的那樣。因此,您的錯誤似乎已得到修復 - 儘管我無法確定該修復程序是否會隨 Visual Studio 2015 Update 2 一起提供。

非同步變數擷取問題已由pull request #7693修復,但DataFlowPass.SetSlotUnassigned此後已被重寫(拆分為 2 個方法並進行了修改),因此我無法確認您發現的迭代器問題是否已由該特定 pull request 或其他一些程式碼更改修復.

引用自:https://stackoverflow.com/questions/35568392