BenZinra BenZinra - 3 months ago 16
Vb.net Question

Expression in With statement becomes Nothing in lambda expression in Task

I have discovered that referencing a member variable in a lambda expression executed in a Task throws a

NullReferenceException
when accessing it using the
With
statement.

For example I expect the following code to print two lines on the console. The first one accesses the
SomeString
member via
obj.SomeString
while the second one uses the
With
statement and accesses the member via
.SomeString
. I expected both options to be equivalent but the second one throws an exception.

Class Foo
Public SomeString As String
End Class

Module Module1

Sub Main()

Dim obj As New Foo With {.SomeString = "Hello World"}

With obj
Task.Factory.StartNew(
Sub()
Console.WriteLine("1:" + obj.SomeString) ' works
Console.WriteLine("2:" + .SomeString) ' NullReferenceException here
End Sub)
End With

Console.ReadKey()

End Sub

End Module


When I move the
Console.ReadKey()
statement into the
With
statement, the code works.

I fixed the actual code by not using the
With
statement but I still don't know what concept I'm missing here. Why can I access members of the
obj
variable in the lambda expression but not the members of the
With
expression? It has not been garbage collected because I can still see it in the debugger when the exception is thrown. The expression seems to go out of scope (or something like that) but why doesn't the compiler just do what I expect and treats it the same as
obj
?

Answer

It is because of the voodoo that the VB compiler does to support the With block and lambda expressions. If you look at your code through a decompiler like Redgate's Reflector, your code gets converted into something like the code below except that I renamed the variables to ones supported by VB; they can be quite long and include characters that are invalid for VB variable names

<STAThread> _
Public Shared Sub Main()
     Dim var1 As New GeneratedClass1
     Dim foo As New Foo With {.SomeString = "Hello World"}
     var1.objVar = foo

     Dim var2 As New GeneratedClass1.GeneratedClass2 With {.var2 = var1, .theWithVariable = var1.objVar}
     Task.Factory.StartNew(New Action(AddressOf var2._Lambda___1))
     var2.theWithVariable = Nothing
     Console.ReadKey()
End Sub

<CompilerGenerated> _
Friend Class GeneratedClass1
     ' Methods
     <DebuggerNonUserCode> _
     Public Sub New()
     End Sub

     <DebuggerNonUserCode> _
     Public Sub New(ByVal other As GeneratedClass1)
          If (Not other Is Nothing) Then
                Me.objVar = other.objVar
          End If
     End Sub


     ' Fields
     Public objVar As Foo

     ' Nested Types
     <CompilerGenerated> _
     Friend Class GeneratedClass2
          ' Methods
          <DebuggerNonUserCode> _
          Public Sub New()
          End Sub

          <DebuggerNonUserCode> _
          Public Sub New(ByVal other As GeneratedClass2)
                If (Not other Is Nothing) Then
                     Me.theWithVariable = other.theWithVariable
                End If
          End Sub

          <CompilerGenerated> _
          Public Sub _Lambda___1()
                Console.WriteLine(("1:" & Me.var2.objVar.SomeString))
                Console.WriteLine(("2:" & Me.theWithVariable.SomeString))
          End Sub


          ' Fields
          Public theWithVariable As Foo
          Public var2 As GeneratedClass1
     End Class
End Class

You can see that the compiler creates a class that holds a reference to the With variable and the method of the lambda expression. As soon as the With variable is out of scope it is set to Nothing and hence the null reference expression when task executes.