VB: Referencing a variable by name in a conditional - vb.net

I am an amateur programmer and in working with a friend and cannot find a solution to our coding dilemma.
We need to be able to compare if a Variable matches data found in second variable, but the first Variable to be searched is to be dependent on the contents of a third variable. (The third variable would name the first variable to be searched)
Var1, Var2, Var3 ... Var100 'Each with their own values and datatypes;
Var45 = 25
Vartocheck1 = "Var45"
Vattocheck2 = 25
If Vartocheck1 = Vartocheck2 Then
(Stuff)
End If
Essentially, I was wondering if there was a good way to compare two variables, most likely in an If-then statement, where one of the two variables is decided by a third variable's contents.
The idea is Vartocheck1 would be a string, containing the NAME of the variable whose value I want to check against Vartocheck2. The issue is that the variables in the code (in my example: Var1, Var2, Var3 ... Var100) are defined as the process runs, but an external excel chart, when referenced, can change certain variables during the program's execution. I could accomplish what I need with about a million nested if-then statements, but that is slow and messy, and I am hoping there is another way.
I have looked into arrays, but implementing the massive number and size of the arrays would be daunting and require an entire project rewrite.
Is there any good method for comparing a variable like this?

What you are looking for is a concept called reflection. This SO question explains it:
How to get the variable names types, and values in the current class or Method in VB.net?
Based on that I have quickly created the following class:
Public Class Class1
Public This As String
Public That As Boolean
Public Function ListVar() As Boolean
Dim fields As System.Reflection.FieldInfo() = Me.GetType().GetFields()
For Each fld As System.Reflection.FieldInfo In fields
Dim name As String = fld.Name
Dim value = fld.GetValue(Me)
Dim typ As Type = fld.FieldType
Debug.Print(name)
Next
Return True
End Function
End Class
You can call the ListVar function from anywhere by doing this:
Dim c As New Class1
c.ListVar()
Obviously this is not production ready, but should give you a start.

While I still think this is an XY problem, one convenient container in .net is a dictionary. This allows you to store key-value pairs which can be of any type. This gives you some of the tools you would get with a database (which may be a better solution in this case). For example :
Imports System.Collections.Generic
Module Module1
Dim ValueDict As New Dictionary(Of String, Integer)
Sub Main()
Dim r As New Random
'Fill the dictionary with keys "Var1" -> "Var100"
'Fill the values with random integers
For i As Integer = 1 To 100
ValueDict.Add("Var" & i.ToString, r.Next)
Next
'Extract a variable by name
Dim extractedVar As Integer
If ValueDict.TryGetValue("Var23", extractedVar) Then
Console.WriteLine("Var23 has value :" & extractedVar.ToString())
Else
Console.WriteLine("Var23 does not exist in the dictionary")
End If
'enumerate all values
For Each valuePair As KeyValuePair(Of String, Integer) In ValueDict
Console.WriteLine("Variable " & valuePair.Key & _
" = " & valuePair.Value.ToString())
Next
'Get a variable by number
Dim varNumber As Integer = 72
If ValueDict.TryGetValue("Var" & varNumber.ToString(), extractedVar) Then
Console.WriteLine("Var" & varNumber.ToString() & _
" has value :" & extractedVar.ToString())
Else
Console.WriteLine("Var" & varNumber.ToString() & _
" does not exist in the dictionary")
End If
Console.ReadLine()
End Sub
End Module
Other types of operations :
'Check if value exists, Assign a new value or update an existing value
Dim newVal As Integer = 12345
Dim varName As String = "Var147"
If Not ValueDict.ContainsKey(varName) Then
Console.WriteLine(varName & " does not currently exist")
End If
ValueDict.Item(varName) = newVal
Console.WriteLine(varName & " now has value :" & ValueDict.Item(varName).ToString())
'Delete a value
ValueDict.Remove(varName)
If Not ValueDict.ContainsKey(varName) Then
Console.WriteLine(varName & " does not currently exist")
End If

Related

VBA dictionary as a property of a class

I have this simplified class named clsWarehouseSum.
Option Compare Database
Option Explicit
Private wh_units As Scripting.Dictionary
Public Function availableUnits(warehouse As String) As Long
'Debug.Print wh_units(warehouse)
If wh_units Is Nothing Then Set wh_units = New Scripting.Dictionary
If Not wh_units.Exists(warehouse) Then
Dim SQL As String
Dim RS As DAO.Recordset
SQL = "SELECT sum(units) as tot_units " _
& "FROM warehouse " _
& "WHERE warehouse = '" & warehouse & "' "
Set RS = CurrentDb.OpenRecordset(SQL)
wh_units.Add (warehouse), RS("tot_units")
End If
availableUnits = wh_units(warehouse)
End Function
I try to use it like this:
Sub test()
Dim wh As New clsWarehouseSum
Debug.Print wh.availableUnits("Cohasset")
Debug.Print wh.availableUnits("Cohasset")
End Sub
While the first Debug.Print prints what's expected, the second one gives me an error:
Run time error 3420, Object Invalid or no longer set. When I step through the code, it correctly evaluates both if statements as false. Yet, the last line of the function gives me the error mentioned above. What am I doing wrong?
Why?
Add Debug.Print TypeName(wh_units(warehouse)) before the availableUnits = wh_units(warehouse) line and if it prints anything else than Long to the Immediate window then you might want to cast to Long using CLng while you also have some error handler in place.
Or, you might want to make sure that the line wh_units.Add (warehouse), RS("tot_units") is adding a Long to your dictionary so you should check the type before you add.
As a general rule, when you return a specific data type from a dictionary or collection, you should always have checks in place either when you add the data to the dict/coll or when you return it so that you avoid type incompatibility and runtime errors.

VBA Function Passing Multi Variables back to Sub

I have a large string over 500 char which is called strEssay. I want to use a function(since I will need to look for several patterns) to return two values if (for example the name) Frank is found or not.
This is the function I'm trying to use:
Function NameFinder(strEssay as String, strName as String)
Dim varNameCounter as Variant
Dim strNameFinderResult as String
varNameCounter = 0
strNameFinderResult = ""
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
End Function
I want to be able to return back to my subroutine both 'strNameFinderResult' and 'varNameFinderCounter'.
Is there any way that I can return both values?
If I can't return both simultaneously can I return one through the function and the other through a textbox or something? What would calling the function look like in the subroutine and/or how would I need to change my function?
NameFinder() function, returning array of 3 elements. It is called and returned by TestMe(), writing the following to the console:
Function NameFinder(essay As String, name As String)
Dim nameFinderResult As String
Dim namefinderCounter As String
nameFinderResult = "" & essay & name
namefinderCounter = 0.001 + 12
NameFinder = Array(nameFinderResult, namefinderCounter, "something else")
End Function
Public Sub TestMe()
Dim myArray As Variant
myArray = NameFinder("foo", "bar")
Dim i As Long
For i = LBound(myArray) To UBound(myArray)
Debug.Print myArray(i)
Next i
End Sub
As a general rule, you have to give the routine a type like this:
Function NameFinder(strEssay as String, strName as String) as string
But, that returns only ONE value.
So, a function (as opposed to a sub) returns one value (as a general rule).
However, you CAN also return parameters that you pass. I mean, in above, you can't make TWO assignments to one variable, can you?
So, you can use a Sub like this:
Sub NameFinder(strEssay as String, strName as String, _
strNameFinderResult as string, _
varNameFinderCounter as double)
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
So in code, you now can go:
dim strMyResult as string
dim strFinderCount as Double
Call NameFinder("MyEassy", "Joe Blow", strMyResult, strFinderCount)
So, you can return values with the parameters.
Now, I suppose it possible for some strange reason, that you want to use a function to return two values with a single assignment?
What you would do is this in your code module.
Define a custom type, and use that.
eg this:
Option Compare Database
Option Explicit
Type SearchResult
strName As String
FindCount As Double
End Type
Function NameFinder(strEssay As String, strName As String) As SearchResult
NameFinder.FindCount = 0
NameFinder.strName = ""
If strEssay Like "*" & strName & "*" Then
NameFinder.strName = strName
NameFinder.FindCount = 1
Else
NameFinder.strName = ""
NameFinder.FindCount = 0.001
End If
End Function
So, now to use in code? You can go like this:
dim MyResults as SearchResult
MyResults = NameFinder("My eassy", "Joe Blow")
debug.print "Name found result = " & MyResults.strName
debug.print "Count of find = " & MyResult.FindCount
The VERY nice thing about above is you get full intel-sense in your code editor.
eg this:
So by building a custom data type, you can use "one" assignment for the return type. And you get nice type checking and inteli-sense in the VBA code editor.
And you can even do this:
But, to get both variables, then you would in theory wind up calling the function two times. So, you can actually use the function without declarer of variables like this:
Debug.Print NameFinder("MyEassy", "Joe blow").strName
Debug.Print NameFinder("MyEassy", "Joe blow").FindCount
So, I don't recommend the above, but in the case in which you ONLY want one of the return values, then the raw expression (function) like above would be a use case (and no need to even declare a return variable).
But, without a doubt, define a custom type in code as per above. The reason is now you get a really nice VBA editor type-checking, inteli-sense, and also that you only have to declare "one" variable that holds two values.
In fact, the results are very much like JavaScript, or even c# in which you declare a "class" type. So with a custom "type" you are declaring a data type of your own. And the beauty of this is if you need say 3 values, then once again you create a type with 3 "inside" values.
The you ONLY have to declare that one variable as the custom type.
With this you get:
Very valuable compile time syntax and data type checking of the var types you are using.
You get GREAT VBA inteli-sense while coding - which means less coding mistakes.
And you type far less typing in the VBA editor as it will pop-up the choices for you as you write code. And you can't type or choose the wrong sub - type, as the compiler will catch this.

VB.Net | Is there a way to reference a dynamic amount of variables as arguments to function/sub?

I'm trying to pass a dynamic amount of variables to a Sub by using ByRef;
Essentially I'm trying to create a module that I can easily import into my projects and make handling the file saving/loading process automated.
The Sub/Function would take a number of variables as references and then loop through them changing each one's value.
I realize I'm missing a crucial point in how visual basic's syntax works but I haven't been able to figure out what I need to do.
The code I've written for this is:
Public Sub LoadSaveToVars(ByRef KeyNamesAndVars() As Object, ByVal FileLoc As String = "")
If isEven(KeyNamesAndVars.Length) Then
Dim Contents As String = My.Computer.FileSystem.ReadAllText(FileLoc)
Dim isOnName As Boolean = True
Dim CurrentVal As String = ""
For i = 0 To KeyNamesAndVars.Length - 1
If isOnName Then
CurrentVal = GetStringValue(KeyNamesAndVars(i), Contents) 'Get the value of the key with the key name in the array
isOnName = False
Else
KeyNamesAndVars(i) = CurrentVal 'Set the variable referenced in the array to the value
isOnName = True
End If
Next
Else
Throw New ArgumentOutOfRangeException("The key names and variables supplied are not even.", "Error loading to variables!")
End If
End Sub
And here's how I try to use this function:
Dim TestVar1 As String = ""
Dim TestVar2 As String = ""
LoadSaveToVars({"key1", TestVar1, "key2", TestVar2})
To keep this question clean I did not include the other functions, but I did make a poor attempt at drawing what I want to happen: https://gyazo.com/eee34b8dff766401f73772bb0fef981a
In the end, I want TestVar1 to be equal to "val1" and TestVar2 to be equal to "val2" and to be able to extend this to a dynamic number of variables. Is this possible?

string modifiers and properties do not work

I am trying to use Methods and Properties of the String Class to modify a string, but I keep getting an Invalid qualifier compile error. I even copied the following code* directly from the MSDN website and it throws the same error.
Public Sub Main()
Dim original As String
original = "aaabbb"
Dim modified As String
modified = original.Insert(3, " ")
End Sub
'This is the original code, but I had to change it slightly because the word-vba
'programming environment didn't like the syntax and highlighted everything red.
'Public Sub Main()
'Dim original As String = "aaabbb"
'Console.WriteLine("The original string: '{0}'", original)
'Dim modified As String = original.Insert(3, " ")
'Console.WriteLine("The modified string: '{0}'", modified)
'End Sub
Does word-vba not support string class modifiers and properties, am I not initializing the string correctly, or is there some other problem?
modified = original.Insert(3, " ")
You're thinking in VB.NET, but writing VBA. Strings (or any primitive or UDT type) don't have members in VBA. Not your fault, finding official VBA documentation is getting harder every day, with every "VBA" search yielding results for VB.NET.
That original code is clearly VB.NET.
If you mean to concatenate 3 spaces in front of original, then what you want to do is this:
modified = String(3, " ") & original
If you mean to get a new string in which a specified string is inserted at a specified index position in this instance (MSDN), then you want to do this (thanks #A.S.H!):
modified = Left$(original, 3) & " " & Right$(original, Len(original) - 3)

How to harness auto-completion of strings?

I'm writing an application in which I have to pass strings as parameters. Like these:
GetValue("InternetGatewayDevice.DeviceInfo.Description")
GetValue("InternetGatewayDevice.DeviceInfo.HardwareVersion")
CheckValue("InternetGatewayDevice.DeviceInfo.Manufacturer")
ScrambleValue("InternetGatewayDevice.DeviceInfo.ModelName")
DeleteValue("InternetGatewayDevice.DeviceInfo.ProcessStatus.Process.1")
The full list is about 10500 entries, and i tought that i'd be really lost in searching if i misspell something.
So I am trying to declare a namespace for every string segment (separated by ".") and declare the last as a simple class that widens to a String of its FullName (except the base app namespace):
Class xconv
Public Shared Widening Operator CType(ByVal d As xconv) As String
Dim a As String = d.GetType.FullName
Dim b As New List(Of String)(Strings.Split(a, "."))
Dim c As String = Strings.Join(b.Skip(1).ToArray, ".")
Return c
End Operator
End Class
So I'd have these declarations:
Namespace InternetGatewayDevice
Namespace DeviceInfo
Class Description
Inherits xconv
End Class
End Namespace
End Namespace
This way IntelliSense is more than happy to autocomplete that string for me.
Now I'd have to do this for every possible string, so I opted (in order to retain my sanity) to make a method that does that:
Sub Create_Autocomlete_List()
Dim pathlist As New List(Of String)(IO.File.ReadAllLines("D:\list.txt"))
Dim def_list As New List(Of String)
Dim thedoc As String = ""
For Each kl As String In pathlist
Dim locdoc As String = ""
Dim el() As String = Strings.Split(kl, ".")
Dim elc As Integer = el.Length - 1
Dim elz As Integer = -1
Dim cdoc As String
For Each ol As String In el
elz += 1
If elz = elc Then
locdoc += "Class " + ol + vbCrLf + _
"Inherits xconv" + vbCrLf + _
"End Class"
Else
locdoc += "Namespace " + ol + vbCrLf
cdoc += vbCrLf + "End Namespace"
End If
Next
locdoc += cdoc
thedoc += locdoc + vbCrLf + vbCrLf
Next
IO.File.WriteAllText("D:\start_list_dot_net.txt", thedoc)
End Sub
The real problem is that this is HORRIBLY SLOW and memory-intense (now i dot a OutOfMemory Exception), and I have no idea on how Intellisense would perform with the (not available in the near future) output of the Create_Autocomlete_List() sub.
I believe that it would be very slow.
So the real questions are: Am I doing this right? Is there any better way to map a list of strings to auto-completable strings? Is there any "standard" way to do this?
What would you do in this case?
I don't know how Visual Studio is going to perform with thousands of classes, but your Create_Autocomlete_List method can be optimized to minimize memory usage by not storing everything in memory as you build the source code. This should also speed things up considerably.
It can also be simplified, since nested namespaces can be declared on one line, e.g. Namespace First.Second.Third.
Sub Create_Autocomlete_List()
Using output As StreamWriter = IO.File.CreateText("D:\start_list_dot_net.txt")
For Each line As String In IO.File.ReadLines("D:\list.txt")
Dim lastDotPos As Integer = line.LastIndexOf("."c)
Dim nsName As String = line.Substring(0, lastDotPos)
Dim clsName As String = line.Substring(lastDotPos + 1)
output.Write("Namespace ")
output.WriteLine(nsName)
output.Write(" Class ")
output.WriteLine(clsName)
output.WriteLine(" Inherits xconv")
output.WriteLine(" End Class")
output.WriteLine("End Namespace")
output.WriteLine()
Next
End Using
End Sub
Note the use of File.ReadLines instead of File.ReadAllLines, which returns an IEnumerable instead of an array. Also note that the output is written directly to the file, instead of being built in memory.
Note Based on your sample data, you may run into issues where the last node is not a valid class name. e.g. InternetGatewayDevice.DeviceInfo.ProcessStatus.Process.1 - 1 is not a valid class name in VB.NET. You will need to devise some mechanism to deal with this - maybe some unique prefix that you could strip in your widening operator.
I'm also not sure how usable the resulting classes will be, since presumably you would need to pass an instance to the methods:
GetValue(New InternetGatewayDevice.DeviceInfo.Description())
It seems like it would be nicer to have Shared strings on a class:
Namespace InternetGatewayDevice
Class DeviceInfo
Public Shared Description As String = "Description"
Public Shared HardwareVersion As String = "HardwareVersion"
' etc.
End Class
End Namespace
So you could just reference those strings:
GetValue(InternetGatewayDevice.DeviceInfo.Description)
However, I think that would be a lot harder to generate without creating name clashes due to the various levels of nesting.