0

I have function with loop

Sub KvmActionForEachVm(CN As MySqlConnection, SshClient As Renci.SshNet.SshClient, Action As Action(Of MySqlConnection, Renci.SshNet.SshClient, String, Integer))
    Dim AdmVMList As List(Of AdmVM) = ReadAdmVMList(CN)
    For Each One As AdmVM In AdmVMList
        Try
            Action.Invoke(CN, SshClient, One.Name, CInt(One.Id))
            'for example ParseVmConfig(CN, SshClient, One.Name, One.Id)
        Catch ex As Exception
            Console.WriteLine(One.Name & ": " & ex.Message)
            Continue For
        End Try
    Next
    SshClient.Disconnect()
End Sub

and various function with the same signature what can be working in loop like below. Of course, this function require correct VmName(One.Name) and VmId(One.Id)

Sub ParseVmConfig(CN As MySqlConnection, SshClient As Renci.SshNet.SshClient, VmName As String, VmId As Integer)
....
End sub

Without loop I'm usually pass delegates as parameters by the same way

  KvmActionForEachVm(CN, SshClient, Sub() ParseVmConfig(CN, SshClient, "", 0))

But in this case I'm confused.

Upd. I'm using NET Core 5.0 and this is screen of my application with this issue.

enter image description here

Alex
  • 373
  • 1
  • 3
  • 13
  • @KlausGütter yes, For each in this case – Alex Dec 12 '21 at 06:24
  • It wouldn't necessarily be the most efficient code, but you can create a new lambda on each loop iteration. Unless it's very performance-sensitive, I'd suggest starting with that, since any alternative would likely be more complex and error-prone. – Craig Dec 13 '21 at 18:28
  • Thank you @Craig, I don't carry about performance in this case. Can you show me simple example template (maybe in C# if you don't use VB) - how I can create new lambda from parameter's action. And how action parameters pass with compiler because function inside loop need four parameters, outside function I know only 2 and name of action. – Alex Dec 13 '21 at 22:29
  • I guess I don't really understand where you're confused. You know how to make a new `Action` which calls the old `Action` with some of its parameters specified. You can certainly do that in a loop (with the "fixed" parameters loop variables or things derived from the loop variables). e.g. swap `One.Name` for the empty string in the final code block in your question. Did you try this? Was there some kind of problem with it? – Craig Dec 13 '21 at 22:35
  • Problem with signature, I need to pass action with four parameters because this is signature of method ParseVmConfig. I don't know last two parameters outside the loop. Maybe I need to direct perform Action.Invoke(CN, SshClient, One.Name, CInt(One.Id)). Now this code producing execution of ParseVmConfig with two last dummy parameters. – Alex Dec 13 '21 at 22:46

1 Answers1

0

You didn't indicate which version of .NET / VB you're using. In old versions of .NET, there is an interesting behavior of capture in a loop. It arises from the intersection of the scope of the loop control variable (which is the entire loop, not just an individual iteration) and the fact that variables from the enclosing scope of the lambda are captured by reference and not by value. This can lead to surprising behavior.

If this is the issue you're having (every invocation of the new Action runs as if called by the last loop iteration, or even one past the last loop iteration), then you can fix it by declaring new variables within the loop scope to capture. These variables are scoped by the loop iteration, vs. the loop variable which can have a weird scope of all loop iterations.

e.g.

For Each One As AdmVM In AdmVMList
    Dim vmName = One.Name
    Dim vmId = One.Id

    Call Foo(Sub() Action.Invoke(CN, SshClient, vmName, vmId)
Next

This surprising behavior was addressed in a fairly old version of C# (by making each iteration of the loop get a new copy of the loop control variable), and I'm not sure what its history may be in VB. I would imagine that it was cleaned up ca. 2010 as VB was kept fairly close to C# in that time period, but maybe you're using a very old version of VB, or maybe my imagination is wrong.

Also see:

Related C# question: Lambda variable capture in loop - what happens here?

Eric Lippert on the issue in C# (1): https://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/

Eric Lippert on the issue in C# (2): https://ericlippert.com/2009/11/16/closing-over-the-loop-variable-considered-harmful-part-two/

Craig
  • 2,248
  • 1
  • 19
  • 23
  • Dear @Craig, I use Net Core 5.0. Unfortunately you recipe don't working. Please see this screen - https://drive.google.com/file/d/1ZWRe5l7VZSDmyrSIukhZ3WJ_Zuebrq7s/view I expect inside function ParseVmConfig parameters from loop, however I see dummy parameters. – Alex Dec 13 '21 at 23:40
  • @Alex in that case, this answer probably doesn't apply. In regard to the image you showed, it looks like you're doing some debugging. You need to inspect the contents of `AdmVmList` and `One`. You're not saving a delegate for later, you're invoking it immediately. That's not something that should have any surprises. – Craig Dec 15 '21 at 14:30
  • Dear @Craig. I'm sure that AdmVmList and One has correct value (because I can uncomment next line and receive workable code). Maybe I need to replace line 662 to more sophisticated line with construction dynamic invoke from scratch (instead simple Action.Invoke) and than I need to load One value to last two parameters. But I don't know how to create dynamic invoke correctly in this case. – Alex Dec 15 '21 at 19:44