How to pass parameters to delegate (C#, VB) into loop - vb.net

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.

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/

Related

Running a loop while debugging VBA

The Problem
I am trying to debug some code, and somewhere in the middle I stopped at a breakpoint. Now I want to change some variables and run a certain loop several times.
How far did I get?
I know how to change the variables, but somehow I get stuck when trying to run the loop in the immediate window. Here is an example:
Dim i As Integer
Dim j As Integer
For i = 0 To 6
j=i ' Do something
Next i
I tried several variations of the code, but each time I get the following error:
Compile error: Next without for
Other relevant information
I tried searching but mostly found information about problems with loops, whilst I am quite sure the loop itself is fine. (Especially as I reached it before arriving at the breakpoint).
The only place I saw someone addres this situation, he reduced the loop to a single line, however to do this every time would be very impractical in my case.
I realize that I could call a function containing the loop, and then the function call would probably work, but again this feels quite impractical. So I guess it boils down to the following question.
The question
What is a practical way to run a loop whilst debugging VBA code in Excel?
There is actually a way for using loops or other multi-line statements in the Immediate Window - using a colon : to separate statements instead of a new line.
Full solution is described here.
Note that in the Immediate Window you also don't have to declare the variables using a Dim statement.
To summarize, your snippet would look something like this:
For i = 0 To 6: j=i: debug.Print i+j: Next i
I think I understand your question. You want to run a multi-line code block (i.e. the loop) in the Immediate Window. This throws errors because the Immediate Window is only intended for single lines of code.
I don't have any suggestions other than those you already mentioned. I'd recommend putting your test loop into a separate function and calling that from the Immediate Window:
Sub Test()
Dim i As Integer
Dim j As Integer
For i = 0 To 6
j=i ' Do something
Next i
End
Another option is to set several breakpoints. You can also run one line of code at a time with F8.
What is likely the preferred method (i.e., what most people actually do) is use the full power of the IDE, which includes the Immediate, Locals and Watch panes. You can change the value of most variables at runtime by direct assignment in the Immediate Pane (i=6 will do exactly what you think it should do). The IDE also allows you to set breakpoints, add watch conditions, step through code line-by-line using the F8, step through function or procedure calls using Shift+F8, stepping over (and back) through code using the mouse/cursor, and with a few exceptions, you can even add new variables during runtime.

VB.Net thread seems to be running twice giving file access problems

I have some very basic code that takes a list of strings and a new thread is created per string.
The thread then writes a file named with the string so should be unique but I get access violations.
I try the debugger option in VS 2010 and it doesn't give me the file access problems when I step through but it writes two lines per loop so I am assuming it still has a problem with running the thread twice
Code to call the thread:
For Each x In x_list
Dim trd as new Thread(DirectCast(Sub() tp(x), ThreadStart))
Next
Code for thread:
Private Sub tp(ByVal x_in As String)
Dim res_file As New StreamWriter("C:\result_" + x_in + ".txt", True)
For i = 1 to 5
res_file.WriteLine(DateTime.Now.ToString)
Next
res_file.Close()
End Sub
You're introducing an unnecessary Lambda here:
Sub() tp(x)
And then you're falling foul of the same well known issue from C# - that what you're capturing is the variable that keeps changing until it's set to the last value in the loop.1
Try instead just:
For Each x In x_list
Dim trd as new Thread(DirectCast(tp, ParameterizedThreadStart))
trd.Start(x)
'TODO - Does trd get stored anywhere?
Next
1 See e.g. Closing over the loop variable considered harmful which discusses how there's a change for C#5, but I don't think there's a corresponding change for VB.Net.

How to call another module without returning to the first one after completion?

This is probably the dumbest question I've ever asked here, but it's hard to find answers to things like this.
I have a program with a bunch of modules/subs that each calculate a different variable. They're pretty complex, so I like to keep them separate. Now I want an earlier module to skip to another module based on user input. I thought I could use the call (sub name) method for this, but then the program returns to where the call line was and continues on that module from where it left off.
Example:
Module 1:
Sub NewPracticeSub()
Call otherpracticesub
MsgBox ("We've gone back to this sub... :(")
End Sub
Module 2:
Sub otherpracticesub()
MsgBox ("We're in the other practice sub!")
End Sub
I don't want it to return to Module 1. What can I do to have it switch control to Module 2 without it then returning to complete Module 1 upon completion of Module 2?
I feel like I just used the most confusing language possible to explain all of this, but thank you for your help anyways!!
Edit: I know I used the words module and sub interchangeably, and I know they're different. I like to keep each sub (which are each very large in my program) in their own modules because it's easier to keep track of them, and easier to explain/demonstrate the application flow to other people.
I think all you're looking for is the command Exit Sub which will make the program leave the subroutine without continuing any further, But the way you usually want to do this is, rather than calling a Sub, rather call a Function that returns a boolean value.
So, for example:
Public Function MyFunc() as Boolean
....
If [good] MyFunc = True
Else MyFunc = False
End Function
Then you could do something along the lines of:
Sub MyCallingSub()
...
If MyFunc = True then Exit Sub
Else ...
End Sub
It just adds in A LOT more felxibility and ability to choose whether you want to continue further in your sub or not.
Hope that makes sense.
Other than using the ugly End statement which I will describe below (and strongly recommend you to avoid), I'm not aware of any way to circumvent the call stack. Even John's response necessarily returns to the calling procedure, and evaluates another statement to determine whether to proceed or end.
This may yield undesirable outcomes, which is why I hesitate to recommend it, in favor of properly structuring your code, loops, etc., with respect to the call stack.
In any case, here is how you can use the End statement within your child subroutines, without needing any sort of public/global variables. This still allows you the flexibility to decide when & where to invoke the End statement, so it need not always be invoked.
Sub NewPracticeSub()
Call otherpracticesub, True
MsgBox ("We've gone back to this sub... :(")
End Sub
Sub otherpracticesub(Optional endAll as Boolean=False)
MsgBox ("We're in the other practice sub!")
If endAll then End '## Only invoke End when True is passed to this subroutine
End Sub
Why I say this method should be avoided, via MSDN:
"Note The End statement stops code execution abruptly, without
invoking the Unload, QueryUnload, or Terminate event, or any other
Visual Basic code. Code you have placed in the Unload, QueryUnload,
and Terminate events of forms and class modules is not executed.
Objects created from class modules are destroyed, files opened using
the Open statement are closed, and memory used by your program is
freed. Object references held by other programs are invalidated.
The End statement provides a way to force your program to halt. For
normal termination of a Visual Basic program, you should unload all
forms. Your program closes as soon as there are no other programs
holding references to objects created from your public class modules
and no code executing."
It will always return but that doesn't mean its a problem. I suggest you use Exit Sub as follows:
Sub NewPracticeSub()
Call otherpracticesub
**Exit Sub**
'Nothing more can execute here so its no longer a worry
End Sub
Module 2:
Sub otherpracticesub()
MsgBox ("We're in the other practice sub!")
End Sub

vb.net: Jump out of exceptioned function

I am just reworking my VB6 in .NET.
I have a function that is called NonNullString(byval uAny As Object) As String
In VB6 I worked with a sqlite wrapper, and a recordset's member could be accessed by using
Dim sString$
sString = r("somefield")
(without ".Value")
I have really many of these fields, and I changed most of them to ".Value", but for some I forgot it.
An exception is therefore raised in the function NoNullString, and I am looking for a way to quickly jump out of the function in order to see what the caller was and improve the code.
F5 does not do the job.
Does anybody have any ideas?
Thank you!
Press CTRL+L to see call stack. From there you can navigate through the stack.
You can then use Set Next Statement (CTRL+F9) on the End Function of your errored function. Two times F10 to complete execution of this function. Repeat this step till you are in the scope where you think the error originated. Then, if you are on x86 (so you have Edit&Continue available), fix your code, and drag your currently executed line to the moment when this fix would occur. And then try running your function again.
Unfortunately, you cannot Set Next Statement outside of the current block function/sub, which I was going to suggest originally.

Profiling VBA code for microsoft word

I have some legacy code that uses VBA to parse a word document and build some XML output;
Needless to say it runs like a dog but I was interested in profiling it to see where it's breaking down and maybe if there are some options to make it faster.
I don't want to try anything until I can start measuring my results so profiling is a must - I've done a little searching around but can't find anything that would do this job easily. There was one tool by brentwood? that requires modifying your code but it didn't work and I ran outa time.
Anyone know anything simple that works?
Update: The code base is about 20 or so files, each with at least 100 methods - manually adding in start/end calls for each method just isn't appropriate - especially removing them all afterwards - I was actually thinking about doing some form of REGEX to solve this issue and another to remove them all after but its just a little too intrusive but may be the only solution. I've found some nice timing code on here earlier so the timing part of it isn't an issue.
Using a class and #if would make that "adding code to each method" a little easier...
Profiler Class Module::
#If PROFILE = 1 Then
Private m_locationName As String
Private Sub Class_Initialize()
m_locationName = "unknown"
End Sub
Public Sub Start(locationName As String)
m_locationName = locationName
MsgBox m_locationName
End Sub
Private Sub Class_Terminate()
MsgBox m_locationName & " end"
End Sub
#Else
Public Sub Start(locationName As String)
'no op
End Sub
#End If
some other code module:
' helper "factory" since VBA classes don't have ctor params (or do they?)
Private Function start_profile(location As String) As Profiler
Set start_profile = New Profiler
start_profile.Start location
End Function
Private Sub test()
Set p = start_profile("test")
MsgBox "do work"
subroutine
End Sub
Private Sub subroutine()
Set p = start_profile("subroutine")
End Sub
In Project Properties set Conditional Compilation Arguments to:
PROFILE = 1
Remove the line for normal, non-profiled versions.
Adding the lines is a pain, I don't know of any way to automatically get the current method name which would make adding the profiling line to each function easy. You could use the VBE object model to inject the code for you - but I wonder is doing this manually would be ultimately faster.
It may be possible to use a template to add a line to each procedure:
http://msdn.microsoft.com/en-us/library/aa191135(office.10).aspx
Error handler templates usually include an ExitHere label of some description.. The first line after the label could be the timer print.
It is also possible to modify code through code: "Example: Add some lines required for DAO" is an Access example, but something similar could be done with Word.
This would, hopefully, narrow down the area to search for problems. The line could then be commented out, or you could revert to back-ups.
Insert a bunch of
Debug.Print "before/after foo", Now
before and after snippets that you think might run for long terms, then just compare them and voila there you are.
My suggestion would be to divide and conquer, by inserting some timing lines in a few key places to try to isolate the problem, and then drill down on that area.
If the problem is more diffused and not obvious, I'd suggest simplifying by progressively disabling whole chunks of code one at a time, as far as is possible without breaking the process. This is the analogy of finding speed bumps in an Excel workbook by progressively hard coding sheets or parts of sheets until the speed problem disappears.
About that "Now" function (above, svinto) ...
I've used the "Timer" function (in Excel VBA), which returns a Single.
It seems to work just fine. Larry