views:

56

answers:

2

Just thought I'd share this in case anyone else has run into this.
I did something similar today and it took me a while to figure out why this was causing a problem at runtime.

This code:

Public Class foo
  Public bar As String = "blah"
End Class

Public Sub DoInline()
  Dim o As New foo
  Dim f As Func(Of String)
  With o
    f = Function() .bar
  End With
  Try
    Console.WriteLine(f.DynamicInvoke())
  Catch ex As Reflection.TargetInvocationException
    Console.WriteLine(ex.InnerException.ToString)
  End Try
End Sub

Throws a NullReferenceException. It seems as though the With is using the closure as its temp storage, and at the "End With", it sets the closure's variable to Nothing.

Here is that code in RedGate Reflector:

Public Shared Sub DoInline()
    Dim o As New foo
    Dim $VB$Closure_ClosureVariable_7A_6 As New _Closure$__1
    $VB$Closure_ClosureVariable_7A_6.$VB$Local_VB$t_ref$L0 = o
    Dim f As Func(Of String) = New Func(Of String)(AddressOf $VB$Closure_ClosureVariable_7A_6._Lambda$__1)
    $VB$Closure_ClosureVariable_7A_6.$VB$Local_VB$t_ref$L0 = Nothing 
    Try 
        Console.WriteLine(RuntimeHelpers.GetObjectValue(f.DynamicInvoke(New Object(0  - 1) {})))
    Catch exception1 As TargetInvocationException
        ProjectData.SetProjectError(exception1)
        Console.WriteLine(exception1.InnerException.ToString)
        ProjectData.ClearProjectError
    End Try
End Sub

Notice the

$VB$Closure_ClosureVariable_7A_6.$VB$Local_VB$t_ref$L0 = Nothing 

Only "question" I can really ask is; is this a bug or a strange design decision that for some reason I'm not seeing. I'm pretty much just going to avoid using "With" from now on.

+1  A: 

There's really only one bug that I see, the compiler should generate an error for this. Shouldn't be hard to implement. You can report it at connect.microsoft.com

Hans Passant
Lambdas can be validly createdy and used inside a `With` block. To error here would cause completely valid code to result in an error. True Vb.Net chose a similar for capturing the iteration variable in a `foreach` loop. However in that case there were so many examples of people doing it wrong that we felt it was necessary. We did choose to do it as a warning though so users could suppress it they *believed* the use was valid.
JaredPar
+5  A: 

This behavior is "By Design" and results from an often misunderstood detail of the With statement.

The With statement actually takes an expression as an argument and not a direct reference (even though it's one of the most common use cases). Section 10.3 of the language spec guarantees that the expression passed into a With block is evaluated only once and is available for the execution of the With statement.

This is implemented by using a temporary. So when executing a .Member expressio inside a With statement you are not accessing the original value but a temporary which points to the original value. It allows for other fun scenarios such as the following.

Dim o as New Foo
o.bar = "some value"
With o   
  o = Nothing
  Console.WriteLine(.bar) ' Prints "some value"
End With

This works because inside the With statement you are not operating on o but rather a temporary pointing to the original expression. This temporary is only guaranteed to be alive for the lifetime of the With statement and is hence Nothingd out at the end.

In your sample the closure correctly captures the temporary value. Hence when it's executed after the With statement completes the temporary is Nothing and the code fails appropriately.

JaredPar
I would say both csauve's code and the example you give should not be allowed to compile, period.
BlueRaja - Danny Pflughoeft
oh okay that's starting to make sense. I was aware of the behaviour in your example, I just had never tried to do a closure around a with. THANKS!
csauve
I know the "With" creates a temporary, but I'm not clear why the temporary gets nulled out. If a closure holds a variable that goes out of scope, won't the variable usually remain valid?
supercat
@supercat, the temporary gets nulled out because its a temporary and the VB language wants to make sure the value is eligible for collection once the code block using the temporary is completed. The temporary is indeed lifted into the closure but the language service still nulls out the value.
JaredPar
@JaredPar: Would not the same reasoning imply that variables declared within e.g. an "if" statement would receive the same treatment once execution leaves the scope? Though to be sure, what I've seen of closures reminds me of the bad old days of vb6 where parameters default to "ByRef"; there are times one wants an anonymous method to use the local variables of its parent, but having heap allocations for any local variable that is sometimes used in a lambda (whether or not the lambda executes) doesn't seem like a great idea.
supercat