Class property as ByRef argument not working - vba

I am using a function to modify a series of strings, passing them ByRef as arguments to the modifying function. The caller's string variables are all modified as expected but the one argument which is a class property does not change, should this be possible?
The essentials of the class are:-
Private cRptRef As String
Public Property Get TestRefID() As String
TestRefID = cRptRef
End Property
Public Property Let TestRefID(Test_Ref As String)
cRptRef = Test_Ref
End Property
The function for modifying the strings has the following declaration
Public Function GetTestFileNames(ByRef hdrFile As String, _
ByRef calFile As String, _
ByRef dataFile As String, _
ByRef testRef As String _
) As Boolean
The call to GetTestFilenames is as follows:
If GetTestFileNames(HEADERpath, CALpath, RAWDATApath, _
ref) = False Then
All the string arguments are declared as global strings and are empty ("") before the call. After the call they typicaly have content like "d:{path to file{filename.csv}.
So all these statements in the function populate the target strings OK.
hdrFile = Replace(userFile, "##", PT_Rpt.Info.FindNode(TEST_REF_HDRsuffix).data, , , vbTextCompare)
dataFile = Replace(userFile, "##", PT_Rpt.Info.FindNode(TEST_REF_DATAsuffix).data, , , vbTextCompare)
calFile = Replace(userFile, "##", PT_Rpt.Info.FindNode(TEST_REF_CALsuffix).data, , , vbTextCompare)
But this statement fails to assign anything to its target string
testRef = Mid(userFile, InStrRev(userFile, Application.PathSeparator) + 1)
testRef = Left(testRef, InStrRev(testRef, "_") - 1)
Debug.Print "Class.TestRefID="; testRef
The Debug.Print statement prints the expected string, but the external reference is not affected. Is this something to do with it being a property?
The target string being Class.TestRefID in place of the testRef argument.
If I replace the class property in the argument list with a standard string variable, and then assign that to the class property then it I get the expected result, which seems unnecessary work.
Is there something I'm missing or is this not possible in VBA?

A member access expression is an expression that must first be evaluated by VBA before its result can be passed around.
If I have a Class1 module like this:
Option Explicit
Public Foo As String
And then a quick caller procedure:
Sub test()
With New Class1
bla .Foo
Debug.Print .Foo
End With
End Sub
Sub bla(ByRef bar As String)
bar = "huh"
End Sub
The test procedure will output an empty string.
The reason for this is because when you pass a member to a ByRef parameter of a procedure in VBA, you're not passing a reference to the member - you're passing a reference to the value held by that member.
So the member access expression is evaluated, evaluates to "", so "" is passed ByRef to the procedure, which assigns it to "huh", but the caller isn't holding a reference to the "" value, so it never gets to see the "huh" string that was assigned.
If I replace the class property in the argument list with a standard string variable, and then assign that to the class property then it I get the expected result, which seems unnecessary work
It's not unnecessary work, it's mandated, otherwise nothing is holding a reference to the result of the member expression.
Then again the real problem is a design issue, pointed out by Warcupine: the function doesn't want to byref-return 4 values, it wants to take a reference to this object, and assign its properties.

Related

How to reference a global variable in VBA

In Access VBA I know you can reference an object by using a string: Me("string"). I want to reference a global variable in a function using a string, thus allowing me to use the function for different states.
e.g. I have invoiceBtn and infoBtn buttons, I want to call the same function but using different variables. There are global variables infoBool and invoiceBool
Private Sub infoBtn_Click()
functionName("info")
End Sub
Private Sub invoiceBtn_Click()
functionName("invoice")
End Sub
in the function:
public infoBool As Boolean
public invoiceBool As Boolean
public Function functionName(typeString As String)
Me(typeString & "Bool") = false
Me(typeString & "Btn").visible = false
End Function
The first Me() doesn't compile, the second Me() does, is there a way to obtain a reference to a variable using a string?
edited: I hope it is clear enough now. This is just example code, not used in my program.
In some cases you can try to use Eval function for evaluating functions, but it doesn't work with variables.
You can use collection for storing values or references to objects, so in this case your code will look like
Public col As Collection
Public Function functionName(typeString As String)
'for scalar data types element should be replaced, cannot change value
col.Remove typeString & "Bool"
col.Add False, typeString & "Bool"
'object references can be used directly
col(typeString & "Btn").Visible = False
End Function
Before using this you should add all desired values or object references
Set col = New Collection
col.Add True, "infoBool"
col.Add Me.InfoBtn, Me.InfoBtn.Name

Reference and Value types

I have read that String was a "reference type", unlike integers. MS website
I tried to test its behavior.
Sub Main()
Dim s As New TheTest
s.TheString = "42"
Dim z As String = s.GimmeTheString
z = z & "000"
Dim E As String = s.TheString
s.GimmeByref(z)
end sub
Class TheTest
Public TheString As String
Public Function GimmeTheString() As String
Return TheString
End Function
Public Sub GimmeByref(s As String)
s = TheString
End Sub
End Class
So I expected :
z is same reference as TheString, thus TheString would be set to "42000"
Then Z is modified by reference by GimmeByref thus Z is set to whatever TheString is
Actual result:
Z="42000"
E="42"
TheString="42"
What point am I missing?
I also tried adding "ByRef" in GimmeByRef : yes obviously the GimmeByRef does work as expected, but it also does if I put everything as Integer, which are said to be "Value type".
Is there any actual difference between those types?
The confusion comes about because regardless of type, argument passing in VB is pass by value by default.
If you want to pass an argument by reference, you need to specify the argument type as ByRef:
Public Sub GimmeByref(ByRef s As String)
You also need to understand the difference between mutating a value and re-assigning a variable. Doing s = TheString inside the method doesn’t mutate the value of the string, it reassigns s. This can obviously be done regardless of whether a type is a value or reference type.
The difference between value and reference types comes to bear when modifying the value itself, not a variable:
obj.ModifyMe()
Strings in .NET are immutable and thus don’t possess any such methods (same as integers). However, List(Of String), for instance, is a mutable reference type. So if you modify an argument of type List(Of String), even if it is passed by value, then the object itself is modified beyond the scope of the method.
Strings are immutable, every time you do a change it creates a new "reference" like if New was called.
A String object is called immutable (read-only), because its value
cannot be modified after it has been created. Methods that appear to
modify a String object actually return a new String object that
contains the modification. Ref
Your code basically does something like this:
Sub Main()
Dim a, b As String
a = "12"
b = a
a = a & "13"
Console.WriteLine(a) ' 1213
Console.WriteLine(b) ' 12
Console.ReadLine()
End Sub

How can a Sub update it parameters?

I have some simple code and I understand what it does but not why. I have a Sub and it calls another Sub called CheckIfNothing(oList). oList is a List(Of String). The Sub CheckIfNothing checks each String and if it it Nothing it will make it "". This is the code:
Public Function GiveList(oList As List(Of String))
CheckIfNothing(oList)
Return oList
End Function
Public Sub CheckIfNothing(oList As List(Of String))
For Each s As String In oList
If s Is Nothing Then
s = ""
End If
Next
End Sub
So in GiveList I call CheckIfNothing and I don't return anything from CheckIfNothing and still, the oList in GiveList has no Strings that are Nothing.
I always thought you had to return the value you changed in the called function and set the value again in the sub you call the function in like this: oList = CheckIfNothing(oList). CheckIfNothing would be a function in this case.
Why isn't this necessary, and is this only in VB.NET or also the case in C#?
Maybe this will help explain your question. It is from MSDN regarding Visaul Basic 2013.
When passing an argument to a procedure, be aware of several different distinctions that interact with each other:
•Whether the underlying programming element is modifiable or nonmodifiable
•Whether the argument itself is modifiable or nonmodifiable
•Whether the argument is being passed by value or by reference
•Whether the argument data type is a value type or a reference type
For more information, see Differences Between Modifiable and Nonmodifiable Arguments (Visual Basic) and Differences Between Passing an Argument By Value and By Reference (Visual Basic).
This code is an example of how you can use () around your parameter to protect it from being changed.
Sub setNewString(ByRef inString As String)
inString = "This is a new value for the inString argument."
MsgBox(inString)
End Sub
Dim str As String = "Cannot be replaced if passed ByVal"
' The following call passes str ByVal even though it is declared ByRef.
Call setNewString((str))
' The parentheses around str protect it from change.
MsgBox(str)
' The following call allows str to be passed ByRef as declared.
Call setNewString(str)
' Variable str is not protected from change.
MsgBox(str)
Passing Arguments by Value and by Reference (Visual Basic) 2013

Working with strings in visual basic 2010

I have to create a class in Visual Basic called StringWork.
Public Class StringWork
Now i wrote a shared function in the class called Working that can take a string or a string and Boolean.
Public Shared Function Working(ByVal SingleString as string, optional BValu as Boolean = true)as string
if(working(SingleString))then
'The handling of the string
else if (working(SingleValue, BValue) then
'do something else with string
end if
end function
The function I wrote is returning a string.
Can I access the string passed and edit characters in the string or change the position of characters?
You use that optional parameter to determine how you handle that string:
Public Shared Function Working(ByVal singleString as string, _
Optional bValue as Boolean = True) As String
If bValue Then
'Handle the true part manipulating the result string
Else
'Handle the false part manipulating the result string
End If
End Function
If you call this function like this:
Dim test As String = StringWork.Working("I am Spartacus")
it will call that Working function with bValue = true.
What bValue is suppose to represent isn't very clear from the code nor the post.
In VB.NET, and other .NET languages, strings are immutable. Typically, if you need to modify a string that is passed to your method, you would return the modified string. If however, you need it to modify the parameter, you can specify that it is a "ByRef" argument, in which case you will be able to set it to point to a new string object which will affect the variable that was passed into the method as a parameter. If you need a truly mutable string, you will need a character array or a StringBuilder object.

Get the name of an object and passing parameters

For example if I have a function call like below
Function callingMe()
Exit Function
and my Function call is like below
SomeObj.callingMe('1','2','3','4','5') // Variable number of arguments
Inside callingMe() function, I want to know arguments passed i.e 1,2,3,4,5 and the Object i.e SomeObj in above case.
The method declaration needs to be explicit, to be able to accept n parameters.
In this case, ParamArray can be used.
EDIT: The name of the variable cannot be determined, as the name is immaterial to the runtime. You can refer to the instance of the class using Me keyword.
EDIT2: Assuming that someObj is an instance of a class Person, which has a property named FirstName, you could use Me.FirstName inside callingMe (which is a method in Person class).
Sub Main
dim someObj as Person
someObj = new Person
someObj.FirstName = "Hello"
someObj.callingMe("1","2","3")
End Sub
' Define other methods and classes here
Class Person
Private firstNameValue As String
Public Property FirstName() As String
Get
Return firstNameValue
End Get
Set(ByVal value As String)
firstNameValue = value
End Set
End Property
Function callingMe(paramarray args() as string)
Console.WriteLine(me.FirstName + "," + args(0))
End Function
End Class
You can use a ParamArray to accept a variable number of arguments.
Update according to your question in the comments: To get a reference to the caller you must pass it in as a separate argument. Please note that all fixed parameters must come before the ParamArray
Function callingMe(caller As Object, ParamArray parameters() As String)
For i as Integer = 0 to parameters.Length -1
Console.WriteLine(parameters(i))
Next i
End Function
// Method call:
SomeObj.callingMe(Me, "1", "2", "3", ...)