VB.NET迭代器函数丢失局部变量

rok*_*ken 9 .net vb.net msbuild visual-studio compiler-bug

几个 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
Run Code Online (Sandbox Code Playgroud)

所以,这非常可怕.

值得注意的是,此代码的C#等效执行没有问题.更重要的是,这在VB编译器的早期版本下工作(并且已经工作).比较两个不同编译器创建的状态机之间的MSIL,新编译器似乎几乎只使用.locals用于本地变量存储,而旧编译器在状态机上使用可变字段来保存本地值.

我错过了什么吗?我无法在VB中找到任何与迭代器发生重大变化的文档(我也无法想象会出现这种情况),但也没有发现其他人遇到过这个问题.

这个特殊的例子可以通过将构造移动map到第一个foreach循环之后来解决,但是我担心的是我对这个问题的真实味道没有任何意识.我没有兴趣修改代码"只是让它工作." 在我们广泛的代码库中的其他地方,我可能会遇到同样的问题吗?我已经在Connect上提交了这个问题,但这通常感觉像是一个黑洞.

UPDATE

有人刚刚在Roslyn GitHub页面上报告了与异步状态机相同的问题:https: //github.com/dotnet/roslyn/issues/9001

希望这开始得到一点关注.

Kir*_*kiy 1

首先,感谢您关注我向 Roslyn 团队提出的问题。

我已经从https://github.com/dotnet/roslyn (主分支)提取了最新的 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
Run Code Online (Sandbox Code Playgroud)

XElement由于使用情况,这可能看起来像一团混乱XCData,但这是其他 Roslyn 单元测试使用的格式。

我只对您在问题中发布的代码进行了一项更改 - 即替换Console.ReadKey()Console.WriteLine("done"),以便我可以跟踪成功完成情况(只是CompileAndVerify忽略异常)。

以上测试通过。没有NullReferenceException访问map.Values,输出是:

1
完毕

...正如预期的那样。因此,您的错误似乎已得到修复 - 尽管该修复是否会随 Visual Studio 2015 Update 2 一起提供,我无法确定。

异步变量捕获问题已由Pull request #7693修复,但DataFlowPass.SetSlotUnassigned此后已被重写(分为 2 个方法并进行修改),因此我无法确认您发现的迭代器问题是否已由该特定 Pull Request 或其他一些代码更改修复。

  • Update 2 CTP 确实解决了这个问题。我给微软支持人员打了电话,以获取有关具体修复的更多详细信息,并将根据我提供的任何信息再次更新。遗憾的是,2015/4.6 感觉像是一篇很长的 CTP。平台稳定性曾经是 .NET 的一大卖点...... (2认同)