I have interesting situation with passing two-dimensional array through functions.
Array is declared at form's level scope:
I try to rewrite a part of my code from VB6 where I have workable example.
Dim myArray(,) As Double
Then I get a sub where array is redimed and filled according to data, something like this, symbolic situation:
Public Sub mySub(ByVal myArray(,) As Double)
Dim temparray() As Double = {3, 5, 7, 9}
For a As Double = 0 temparray.length - 1
ReDim Preserve myarray(2, temparray(a))
Next a
myArray(1, 5) = 3.14
... etc...
End Sub
And finally, I would like to fill and read data in array from other sub:
mySub(myArray)
Debug.Print(myArray(1, 5))
And here I get error message:
Object reference not set to an instance of an object.
Data in mySub is filled properly but I can't see this data in calling sub.
What do I do wrong and how can I get this scenario working?
You can solve it by doing this:
Public Sub mySub(ByRef myArray(,) As Double)
'...
End Sub
You need to reference the variable in order to have the changes outside the Sub.
Related
my Excel-applicatin has a module with utility functions. One of them adds items to arrays:
Public Sub addToArray(ByRef arr As Variant, item As Variant)
'Sub adds one element to a referenced array
On Error Resume Next
Dim bd As Long
bd = UBound(arr)
If Err.Number = 0 Then
ReDim Preserve arr(bd + 1)
Else
ReDim Preserve arr(0)
End If
arr(UBound(arr)) = item
End Sub
This Sub works perfectly as long as I pass arrays that are not referenced as object members.
addToArray arr, item
works but...
addToArray myObject.arr, item
doesn't...
the second call adds the item to an array but loses the reference to myObject
I can write a workaround by implementing a method in each class (doesn't need object references because it accesses properties of the same object) but that's not how I wanted to solve this problem.
Pls hälp ;)
Unfortunately, this is not possible due to limitations of VBA.
When you're accessing a public variant field of an object, it's get copied by value, so the original reference is not exposed. And if you declared an array (which is internally a reference type) as a public field, you would get the compile error "Constants, fixed-length strings, arrays, user-defined types and Declare statements not allowed as Public members of object modules"
I have a Sub with an array of strings as parameter:
Private Sub des(ByVal array() As String)
Dim i As Integer
For i = 0 To UBound(array)
array(i) = "hy"
Next
End Sub
When I call the function inside my main function, the value of str changes even if the array is passed to the function ByVal:
Dim str() As String
str = {"11111", "22222", "33333", "44444", "5555", "66666"}
des(str)
I tried making a copy of the array in the Sub, but it still changes in the main function.
Private Sub des(ByVal array() As String)
Dim i As Integer
Dim array2() As String
array2 = array
For i = 0 To UBound(array)
array(i) = "hy"
Next
End Sub
I read on a site that you cannot pass arrays ByVal. Is this true? If so, how should I proceed?
Try this:
Dim i As Integer
Dim array2() As String
array2 = array.Clone()
For i = 0 To UBound(array2)
array2(i) = "hy"
Next
The key difference is the .Clone(), that actually makes a shallow copy of array, and changing the values in array2 will no longer affect your str value in the main code.
Arrays are reference types. That means that when you pass an Array to your function, what is passed is always a reference, not a copy of the array. The Array in your function refers to the same array object as the Array in your calling code.
The same thing happens when you do the assign (it is not a copy!) in your second example: all you've done is make yet another reference to the same object. That is why Boeckm's solution works -- the Clone() call does make a new array and assign it values which are copies of the values in the original array.
In Visual Basic .NET, regarding arrays as parameters, there are two important rules you have to be aware of:
Arrays themselves can be passed as ByVal and ByRef.
Arrays' elements can always be modified from the function or subroutine.
You already know that you can modify the elements of an array inside a subprocess (subroutine or function), no matter how that array is defined as parameter inside that subprocess.
So, given these two subroutines:
Private Sub desval(ByVal array() As String)
array = {}
End Sub
Private Sub desref(ByRef array() As String)
array = {}
End Sub
And this very simple auxiliary subroutine (here I'll use the Console):
Private Sub printArr(ByVal array() As String)
For Each str In array
Console.WriteLine(str)
Next
End Sub
you can do these simple tests:
Dim arr1() as String = {"abc", "xyz", "asdf"}
Console.WriteLine("Original array:")
printArr(arr1)
Console.WriteLine()
Console.WriteLine("After desval:")
desval(arr1)
printArr(arr1)
Console.WriteLine()
Console.WriteLine("After desref:")
desref(arr1)
printArr(arr1)
I read on a site that you cannot pass arrays ByVal. Is this true?
No, that is not true.
An array in the .NET framework is a reference type. When you create an array, an object of System.Array will be created and its reference is assigned to the reference variable.
When you call a des method, the reference of the array object will be passed. In des method, ByVal parameter is a reference parameter variable of type System.Array, and it receive a copy of reference of an array object.
MSDN article - Passing Arguments by Value and by Reference (Visual Basic)
I'm currently stuck on an issue regarding declaring an array of type short in a structure and having it default to 'nothing' rather than '0' after ReDim.
'Declaring array and setting it's initial size
Private Structure Totals_T
Dim sTot_Desc As String
<VBFixedArray(10)> Dim iTot_Cnt() As Short
Public Sub Initialize()
ReDim iTot_Cnt(10)
End Sub
End Structure
Private m_Totals() As Totals_T 'Define the array
'Calling the structure for the two variables declared in the structure
If iNewCnt = 1 Then
ReDim m_Totals(10)
**m_Totals(0).**iTot_Cnt(iColumn_No) = m_Totals(0).iTot_Cnt(iColumn_No) + 1
When calling m_Totals(0) the arrays returned have 10 records in the arry with sTot_Desc and iTot_Cnt having values of nothing in all records.
When I ReDim m_Totals both the variables I declared in the structure(sTot_Desc and iTot_Cnt) are declared as nothing, it's fine for the String but I need the Short I declared to be declared as '0', which is what I thought happens when you ReDim. Can anyone see what's going on here and why it's declaring my variables as 'nothing' rather than defaulting to '0' for the short and ""/nothing for the string?
Any help would be great!
The first issue is that you are not calling the Initialize method after ReDimming the structure array. You can fix this like:
For Each total As Totals_T In m_Totals
total.Initialize()
Next
The second issue is that strings are always initialized to nothing; you need to explicitly set them to empty string if you want anything to happen. You can fix this by changing the Initialize method:
Public Sub Initialize()
sTot_Desc = String.Empty
ReDim iTot_Cnt(10)
End Sub
I have a Sub with an array of strings as parameter:
Private Sub des(ByVal array() As String)
Dim i As Integer
For i = 0 To UBound(array)
array(i) = "hy"
Next
End Sub
When I call the function inside my main function, the value of str changes even if the array is passed to the function ByVal:
Dim str() As String
str = {"11111", "22222", "33333", "44444", "5555", "66666"}
des(str)
I tried making a copy of the array in the Sub, but it still changes in the main function.
Private Sub des(ByVal array() As String)
Dim i As Integer
Dim array2() As String
array2 = array
For i = 0 To UBound(array)
array(i) = "hy"
Next
End Sub
I read on a site that you cannot pass arrays ByVal. Is this true? If so, how should I proceed?
Try this:
Dim i As Integer
Dim array2() As String
array2 = array.Clone()
For i = 0 To UBound(array2)
array2(i) = "hy"
Next
The key difference is the .Clone(), that actually makes a shallow copy of array, and changing the values in array2 will no longer affect your str value in the main code.
Arrays are reference types. That means that when you pass an Array to your function, what is passed is always a reference, not a copy of the array. The Array in your function refers to the same array object as the Array in your calling code.
The same thing happens when you do the assign (it is not a copy!) in your second example: all you've done is make yet another reference to the same object. That is why Boeckm's solution works -- the Clone() call does make a new array and assign it values which are copies of the values in the original array.
In Visual Basic .NET, regarding arrays as parameters, there are two important rules you have to be aware of:
Arrays themselves can be passed as ByVal and ByRef.
Arrays' elements can always be modified from the function or subroutine.
You already know that you can modify the elements of an array inside a subprocess (subroutine or function), no matter how that array is defined as parameter inside that subprocess.
So, given these two subroutines:
Private Sub desval(ByVal array() As String)
array = {}
End Sub
Private Sub desref(ByRef array() As String)
array = {}
End Sub
And this very simple auxiliary subroutine (here I'll use the Console):
Private Sub printArr(ByVal array() As String)
For Each str In array
Console.WriteLine(str)
Next
End Sub
you can do these simple tests:
Dim arr1() as String = {"abc", "xyz", "asdf"}
Console.WriteLine("Original array:")
printArr(arr1)
Console.WriteLine()
Console.WriteLine("After desval:")
desval(arr1)
printArr(arr1)
Console.WriteLine()
Console.WriteLine("After desref:")
desref(arr1)
printArr(arr1)
I read on a site that you cannot pass arrays ByVal. Is this true?
No, that is not true.
An array in the .NET framework is a reference type. When you create an array, an object of System.Array will be created and its reference is assigned to the reference variable.
When you call a des method, the reference of the array object will be passed. In des method, ByVal parameter is a reference parameter variable of type System.Array, and it receive a copy of reference of an array object.
MSDN article - Passing Arguments by Value and by Reference (Visual Basic)
How do I get a public array whose values are set within a subroutine and do not get cleared at the end of the sub in which they were set?
I tried to get:
Public GlobalArray() as Variant
Sub One()
ReDim GlobalArray(0 to 2)
GlobalArray=Array("0","1","2")
End Sub
Sub Two()
Check = GlobalArray(2)
End Sub
such that Check = 2, but I get thrown an error in sub Two complaining about a lack of values in GlobalArray (in fact, even sub One complains that there is no GlobalArray to put things in).
Basically, I have a procedure (One) pulling data from disparate sources, doing some stuff with it, letting the user do some things in Excel, and then running a new subroutine (Two) that uses both the user's input and some of the arrays from sub One.
The Public GlobalArray() variable must be declared in a module. It will not work if it is declared at the top of either a Worksheet or the ThisWorkbook module. Try:
'// Must be declared in a module
Public GlobalArray() As Integer
'// These routines can be in worksheet/thisworkbook modules along side events etc Or in a module
Sub One()
ReDim GlobalArray(0 To 2)
GlobalArray(0) = 0
GlobalArray(1) = 1
GlobalArray(2) = 2
End Sub
Sub Two()
check = GlobalArray(2)
MsgBox (check)
End Sub
Instead of a public variable you could pass it to the second function:
Sub One()
Dim GlobalArray(0 To 2) As Integer
GlobalArray(0) = 0
GlobalArray(1) = 1
GlobalArray(2) = 2
Two GlobalArrayToMe:=GlobalArray
End Sub
Sub Two(ByRef GlobalArrayToMe() As Integer)
check = GlobalArrayToMe(2)
MsgBox (check)
End Sub
This is not VBA and won't compile: GlobalArray=("0","1","2")
You could instead use: GlobalArray = Array("0", "1", "2")
but that requires declaring Public GlobalArray() As Variant
Otherwise assign the array elements one by one as in #Readfidy's answer.