Redim Preserve VBA Alternatives - arrays

Background
I just read a comment on this question that states that Redim Preserve is expensive and should be avoided. I use Redim Preserve in many scenarios, let us say for example to save field names from a PT that meet some specific criteria to use them later on with an API for Access/Selenium/XlWings,etc. where I need to access the elements in the array at different times, thus not looping in the original sheet(s) where PT(s) are; I use them to save data that came outside Excel too. This is to save the time to redo verification/processes and everything that was considered by saving the array in the first place.
Research/thoughts
I have seen that a similar question was asked at VB.net where they suggest List(Of Variable) but I do not think this may be achieved within Excel. I Erase them once they are not longer needed too. In addition, where it is possible, I try to use dictionaries instead of arrays, but it may not be always the case where it is easier to go by index numbers and there is a need for array and not dictionaries. I was thinking that I may be able to create a sheet with the specified items instead of saving them to an array, but I do not see the benefit of doing so in terms of memory saving.
Question
What would be the best alternative to Redim Preserve in VBA?

The intent of Ben's comment is that you should avoid excessive use of Preserve.
Where Arrays are a good design choice, you can and should use them. This is especially true when extracting data from an Excel sheet.
So, how to avoid excessive use of Preserve?
The need to Redim Preserve implies you are collecting data into an array, usually in a loop. Redim without Preserve is pretty fast.
If you have sufficient info, calculate the required array size and ReDim it as that size once
If you don't have that info, Redim it to an oversize dimension. Redim Preserve to the actual size once, after the loop
If you must Redim Preserve in the loop, do it infrequently in large chunks
Beware of premature optimisation. If it works fast enough for your needs, maybe just leave it as is

Update for 20 May 2022. An updated version of the class below can be found at
https://github.com/FullValueRider/WCollection
This update has a more extensive collection of methods and is also available as a 32 bit or 64 bit ActiveX.dll (thanks to twinBasic). There are currently 148 passing tests so the problems of things not working should hopefully be avoided.
Please leave any further comments or requests for updates as an issue on the github page.
===============================================
A collection is a good way to go but the default collection is a bit limited.
You may wish to use a wrapped collection which gives you more flexibility.
Class WCollection (but its easy to change the name to List if you prefer)
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "WCollection"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
'Rubberduck annotations
'#PredeclaredId
'#Exposed
Option Explicit
'#ModuleDescription("A wrapper for the collection object to add flexibility")
Private Type State
Coll As Collection
End Type
Private s As State
Private Sub Class_Initialize()
Set s.Coll = New Collection
End Sub
Public Function Deb() As WCollection
With New WCollection
Set Deb = .ReadyToUseInstance
End With
End Function
Friend Function ReadyToUseInstance() As WCollection
Set ReadyToUseInstance = Me
End Function
Public Function NewEnum() As IEnumVARIANT
Set NewEnum = s.Coll.[_NewEnum]
End Function
Public Function Add(ParamArray ipItems() As Variant) As WCollection
Dim myItem As Variant
For Each myItem In ipItems
s.Coll.Add myItem
Next
Set Add = Me
End Function
Public Function AddRange(ByVal ipIterable As Variant) As WCollection
Dim myitem As Variant
For Each myitem In ipIterable
s.Coll.Add myitem
Next
Set AddRange = Me
End Function
Public Function AddString(ByVal ipString As String) As WCollection
Dim myIndex As Long
For myIndex = 1 To Len(ipString)
s.Coll.Add VBA.Mid$(ipString, myIndex, 1)
Next
End Function
Public Function Clone() As WCollection
Set Clone = WCollection.Deb.AddRange(s.Coll)
End Function
'#DefaultMember
Public Property Get Item(ByVal ipIndex As Long) As Variant
If VBA.IsObject(s.Coll.Item(ipIndex)) Then
Set Item = s.Coll.Item(ipIndex)
Else
Item = s.Coll.Item(ipIndex)
End If
End Property
Public Property Let Item(ByVal ipIndex As Long, ByVal ipItem As Variant)
s.Coll.Add ipItem, after:=ipIndex
s.Coll.Remove ipIndex
End Property
Public Property Set Item(ByVal ipindex As Long, ByVal ipitem As Variant)
s.Coll.Add ipitem, after:=ipindex
s.Coll.Remove ipindex
End Property
Public Function HoldsItem(ByVal ipItem As Variant) As Boolean
HoldsItem = True
Dim myItem As Variant
For Each myItem In s.Coll
If myItem = ipItem Then Exit Function
Next
HoldsItem = False
End Function
Public Function Join(Optional ByVal ipSeparator As String) As String
If TypeName(s.Coll.Item(1)) <> "String" Then
Join = "Items are not string type"
Exit Function
End If
Dim myStr As String
Dim myItem As Variant
For Each myItem In s.Coll
If Len(myStr) = 0 Then
myStr = myItem
Else
myStr = myStr & ipSeparator
End If
Next
End Function
Public Function Reverse() As WCollection
Dim myW As WCollection
Set myW = WCollection.Deb
Dim myIndex As Long
For myIndex = LastIndex To FirstIndex Step -1
myW.Add s.Coll.Item(myIndex)
Next
Set Reverse = myW
End Function
Public Function HasItems() As Boolean
HasItems = s.Coll.Count > 0
End Function
Public Function HasNoItems() As Boolean
HasNoItems = Not HasItems
End Function
Public Function Indexof(ByVal ipItem As Variant, Optional ipIndex As Long = -1) As Long
Dim myIndex As Long
For myIndex = IIf(ipIndex = -1, 1, ipIndex) To s.Coll.Count
If ipItem = s.Coll.Item(myIndex) Then
Indexof = myIndex
Exit Function
End If
Next
End Function
Public Function LastIndexof(ByVal ipItem As Variant, Optional ipIndex As Long = -1) As Long
Dim myIndex As Long
For myIndex = LastIndex To IIf(ipIndex = -1, 1, ipIndex) Step -1
If ipItem = s.Coll.Item(myIndex) Then
LastIndexof = myIndex
Exit Function
End If
Next
LastIndexof = -1
End Function
Public Function LacksItem(ByVal ipItem As Variant) As Boolean
LacksItem = Not HoldsItem(ipItem)
End Function
Public Function Insert(ByVal ipIndex As Long, ByVal ipItem As Variant) As WCollection
s.Coll.Add ipItem, before:=ipIndex
Set Insert = Me
End Function
Public Function Remove(ByVal ipIndex As Long) As WCollection
s.Coll.Remove ipIndex
Set Remove = Me
End Function
Public Function FirstIndex() As Long
FirstIndex = 1
End Function
Public Function LastIndex() As Long
LastIndex = s.Coll.Count
End Function
Public Function RemoveAll() As WCollection
Dim myIndex As Long
For myIndex = s.Coll.Count To 1 Step -1
Remove myIndex
Next
Set RemoveAll = Me
End Function
Public Property Get Count() As Long
Count = s.Coll.Count
End Property
Public Function ToArray() As Variant
Dim myarray As Variant
ReDim myarray(0 To s.Coll.Count - 1)
Dim myItem As Variant
Dim myIndex As Long
myIndex = 0
For Each myItem In s.Coll
If VBA.IsObject(myItem) Then
Set myarray(myIndex) = myItem
Else
myarray(myIndex) = myItem
End If
myIndex = myIndex + 1
Next
ToArray = myarray
End Function
Public Function RemoveFirstOf(ByVal ipItem As Variant) As WCollection
Set RemoveFirstOf = Remove(Indexof(ipItem))
Set RemoveFirstOf = Me
End Function
Public Function RemoveLastOf(ByVal ipItem As Variant) As WCollection
Set RemoveLastOf = Remove(LastIndexof(ipItem))
Set RemoveLastOf = Me
End Function
Public Function RemoveAnyOf(ByVal ipItem As Variant) As WCollection
Dim myIndex As Long
For myIndex = LastIndex To FirstIndex Step -1
If s.Coll.Item(myIndex) = ipItem Then Remove myIndex
Next
Set RemoveAnyOf = Me
End Function
Public Function First() As Variant
If VBA.IsObject(s.Coll.Item(FirstIndex)) Then
Set First = s.Coll.Item(FirstIndex)
Else
First = s.Coll.Item(FirstIndex)
End If
End Function
Public Function Last() As Variant
If VBA.IsObject(s.Coll.Item(LastIndex)) Then
Set Last = s.Coll.Item(LastIndex)
Else
Last = s.Coll.Item(LastIndex)
End If
End Function
Public Function Enqueue(ByVal ipItem As Variant) As WCollection
Add ipItem
Set Enqueue = Me
End Function
Public Function Dequeue() As Variant
If VBA.IsObject(s.Coll.Item(FirstIndex)) Then
Set Dequeue = s.Coll.Item(FirstIndex)
Else
Dequeue = s.Coll.Item(FirstIndex)
End If
Remove 0
End Function
Public Function Push(ByVal ipitem As Variant) As WCollection
Add ipitem
Set Push = Me
End Function
Public Function Pop(ByVal ipitem As Variant) As Variant
If VBA.IsObject(s.Coll.Item(FirstIndex)) Then
Set Pop = s.Coll.Item(FirstIndex)
Else
Pop = s.Coll.Item(FirstIndex)
End If
Remove s.Coll.Count
End Function
Public Function Peek(ByVal ipIndex As Long) As Variant
If VBA.IsObject(s.Coll.Item(FirstIndex)) Then
Set Peek = s.Coll.Item(FirstIndex)
Else
Peek = s.Coll.Item(FirstIndex)
End If
End Function

The custom collection shown in another answer looks like a helpful tool. Another one I recently came across is the BetterArray class, found here. Rather than extending the built-in collection, it extends the built-in array. I posted an answer reviewing it and a couple of other options (the ArrayList, and expansion in chunks) here.
A Collection of Array Rows
One other approach is to use a 1d array for each row of data, and add rows into a collection. When done, the result can be dumped into a 2d array. With a function for making the conversion on hand, the process can be convenient and reasonably efficient.
Function ArrayFromRowCollection(source As Collection) As Variant
'Convert a collection of 1d array rows to a 2d array
'The return array will have the max number of columns found in any row (if inconsistent, a warning is printed)
'Any non-array values in the collection will be entered in the first column of the return array (with warning printed)
'Any objects or multidimensional arrays in the collection will cause an error
Dim sourceCount As Long: sourceCount = source.Count
If sourceCount > 0 Then
'Scan for the max column count across all rows; wrap non-arrays in an array with a warning
Dim itmRow As Variant, itmIndex As Long
Dim arrayBound As Long, tempBound As Long, inconsistentBounds As Boolean
For Each itmRow In source
itmIndex = itmIndex + 1
If VarType(itmRow) < vbArray Then 'An array has a vartype of at least the vbArray constant (8192)
source.Add Array(itmRow), , itmIndex
source.Remove itmIndex + 1 'Wrap non-array element in 1d array so it is in the expected format for later
Debug.Print "ArrayFromRowCollection Warning: Non-array item found and entered in first array column (item " & itmIndex & ")"
Else
tempBound = UBound(itmRow)
If arrayBound <> tempBound Then
If itmIndex > 1 Then inconsistentBounds = True 'This prompts a warning below
If tempBound > arrayBound Then arrayBound = tempBound 'Take the new larger bound, in search of the max
End If
End If
Next
If inconsistentBounds Then Debug.Print "ArrayFromRowCollection Warning: Inconsistent column counts found."
'Create 2d array
Dim i As Long, j As Long
Dim returnArray() As Variant
ReDim returnArray(sourceCount - 1, arrayBound)
For Each itmRow In source
For j = 0 To UBound(itmRow)
returnArray(i, j) = itmRow(j)
Next
i = i + 1
Next
ArrayFromRowCollection = returnArray
Else
ArrayFromRowCollection = Array() 'Empty array for empty collection
End If
End Function
A quick demo, creating an array of data from a directory.
Sub GatherDirectoryInfo()
'Gather directory info in a collection of 1d array rows
Dim tempDir As String, dirPath As String, tempFull As String
dirPath = "C:" & Application.PathSeparator
tempDir = Dir(dirPath, vbDirectory) 'This gets basic files and folders (just the first with this call)
Dim tempCollection As Collection: Set tempCollection = New Collection
tempCollection.Add Array("Parent Folder", "Name", "Type", "File Size", "Last Modified") 'Headers
Do While tempDir <> ""
tempFull = dirPath & tempDir
tempCollection.Add Array(dirPath, tempDir, IIf(GetAttr(tempFull) And vbDirectory, "Folder", ""), Round(FileLen(tempFull) / 1024, 0) & " kb", FileDateTime(tempFull))
tempDir = Dir()
Loop
'Transfer collection to 2d array
Dim DirArray As Variant
DirArray = ArrayFromRowCollection(tempCollection)
End Sub

Related

VBA 1D array ("array or user defined type expected")

I need to make an array of numbers from "n" to "n2" to use it in an IF function like
If ArrayContainsItem([transpose(row(320:420))], WidthArray) Then
'Do stuff
End If
And I am wondering, how do you write that first array (which must be written inside that line directly and not dimensioned prior)? It returns an error "array or user-defined type expected"
I got it working with
dim arr() as variant
arr = [transpose(row(320:420))]
'Main function
If ArrayContainsItem(arr, WidthArray) Then
'Do stuff
End If
End Sub
But I need it to be made up directly inside the if function line like in the first code example.
The function I am using looks like (can't be altered in any way)
Function ArrayContainsItem(ArrayBig() As Variant, ArraySmall() As Variant) As Boolean
'Declare variables
Dim iOption As Long
'Set variables
ArrayContainsItem = False
'Main function
For iOption = 2 To UBound(ArraySmall)
For Each Item In ArrayBig
If CStr(Item) = ArraySmall(iOption) Then
ArrayContainsItem = True
End If
Next Item
ArrayContainsItem = False
Next iOption
End Function
Changing the function to
Function ArrayContainsItem(ArrayBig As Variant, ArraySmall() As Variant)
returns error 2015 on ArrayBig and item is empty.
Would, someone, please help me figure this one out?
Reproducable example
Sub ArrayTesting()
'Old variables
Dim WS_MS As Worksheet
Set WS_MS = ThisWorkbook.Worksheets("Machine Specification")
Dim LowerFilmWidthArray() As Variant
Dim CurrentParameter As Range
Dim ParametersColumn As Long
ParametersColumn = 2
Dim LastColumn As Long
LastColumn = 4
'Width
Set CurrentParameter = WS_MS.Cells.Find("Width", lookat:=xlWhole)
WidthArray = Application.Transpose(Application.Transpose(WS_MS.Range(Cells(CurrentParameter.Row, ParametersColumn), Cells(CurrentParameter.Row, LastColumn)).Value))
'Main function
If ArrayContainsItem([transpose(row320:420)], WidthArray) Then
End If
End Sub
Change your function declaration:
Function ArrayContainsItem(ArrayBig() As Variant, ArraySmall() As Variant) As Boolean
to
Function ArrayContainsItem(ArrayBig As Variant, ArraySmall() As Variant) As Boolean
Sample:
Sub foo()
Dim arraySmall(1 To 3) As Variant
arraySmall(1) = "foo"
arraySmall(2) = "bar"
arraySmall(3) = "baz"
Debug.Print ArrayContainsItem([transpose(row(320:420))], arraySmall) ' Returns False
arraySmall(1) = "1"
arraySmall(2) = "2"
arraySmall(3) = "420"
Debug.Print ArrayContainsItem([transpose(row(320:420))], arraySmall) ' Returns True
End Sub
Function ArrayContainsItem(ArrayBig As Variant, arraySmall() As Variant) As Boolean
'Declare variables
Dim iOption As Long
'Main function
For iOption = 2 To UBound(arraySmall)
Dim i As Long
For i = LBound(ArrayBig) To UBound(ArrayBig)
If CStr(ArrayBig(i)) = arraySmall(iOption) Then
ArrayContainsItem = True
Exit Function '<-- add this
End If
Next
Next iOption
End Function

VBA EXCEL adding array members of specific column to collection for counting unique values

I need a public function to get array and counts values in specific column.
I wrote the following and recives subscription out of range message.
Public Function CountUarrcol(inarr() As Variant, colidx As Integer) As Long
Dim col As New Collection
Dim i As Integer
Dim element As Variant
For i = 0 To UBound(inarr, colidx)
For Each element In inarr(i + 1, colidx)
col.Add Item:=CStr(element.value), Key:=CStr(element.value)
Next
Next i
CountUarrcol = col.Count End Function
Assuming you want to do a count of distinct values within a specified column of an array, here is an example with a 5*3 array read in from a worksheet range, counting the distinct values in column 2. I am using a function by Mark Nold to check if the value to be added already exists in the collection.
Option Explicit
Public Sub test()
Dim testArr()
Dim myCount As Long
testArr = ActiveSheet.Range("A1:C5").Value
myCount = CountUarrcol(testArr, 2)
MsgBox myCount
End Sub
Public Function CountUarrcol(inarr() As Variant, colidx As Long) As Long
Dim col As New Collection
Dim i As Long
For i = 1 To UBound(inarr)
If Not InCollection(col, CStr(inarr(i, colidx))) Then
col.Add Item:=CStr(inarr(i, colidx)), key:=CStr(inarr(i, colidx))
End If
Next i
CountUarrcol = col.Count
End Function
'Mark Nold https://stackoverflow.com/questions/137845/determining-whether-an-object-is-a-member-of-a-collection-in-vba
Public Function InCollection(col As Collection, key As String) As Boolean
Dim var As Variant
Dim errNumber As Long
InCollection = False
Set var = Nothing
Err.Clear
On Error Resume Next
var = col.Item(key)
errNumber = CLng(Err.Number)
On Error GoTo 0
'5 is not in, 0 and 438 represent incollection
If errNumber = 5 Then ' it is 5 if not in collection
InCollection = False
Else
InCollection = True
End If
End Function
I Used two sub routine as follow:
Public Function CountUvalinarrcol(ByRef inarr As Variant, ByVal colidx As Integer) As Long
Dim col As New Collection
Dim i As Integer
Dim element As Variant
For i = 1 To UBound(inarr)
element = inarr(i, colidx)
If colContains(col, element) = False Then
col.Add item:=CStr(element)
End If
Next i
CountUvalinarrcol = col.Count
End Function
The other one is:
Public Function colContains(colin As Collection, itemin As Variant) As Boolean
Dim item As Variant
colContains = False
For Each item In colin
If item = itemin Then
colContains = True
Exit Function
End If
Next
End Function
Calling above functions:
sub test()
dim x as long
x= CountUvalinarrcol(lsarr, 0)
end sub

VBA - Create empty array

I have a function that takes an array of strings and map each string to a Date instance. The function boils down to the following code.
Private Function ParseDates(dates() As String) As Date()
Dim res() As Date
Dim i As Integer
If UBound(dates) >= 0 Then
ReDim res(UBound(dates)) As Date
End If
For i = LBound(dates) To UBound(dates)
res(i) = #01/01/2000#
Next i
ParseDates = res
End Function
The function works just fine as long as the argument dates is nonempty. When dates is empty res is not given a dimension. As a result, the returned value is not enumerable causing to users of this function to crash if the result is enumerated in a loop.
parsedDates = ParseDates(input)
For i = 1 To UBound(parsedDates) ' Suscription out of range
...
How do I instantiate and return an empty array, when dates is empty?
If you call Split("",".") you receive an object with type String(0 to -1). I need my function to return a object of type Date(0 to -1) as Date() is not an actual array.
I have tried with ReDim res(-1) this causes an Subscript out of range error.
This seems to do the trick:
Private Declare Function EmptyDateArray Lib "oleaut32" Alias "SafeArrayCreateVector" (Optional ByVal vt As VbVarType = vbDate, Optional ByVal lLow As Long = 0, Optional ByVal lCount As Long = 0) As Date()
Function emptyDate() as Date()
emptyDate = EmptyDateArray()
End Function
Based on an answer by user wgweto for this question at VBForums.
I've used something like this in the past.
Public Function IS_ARRAY_EMPTY(arrInput As Variant) As Boolean
Dim lngTemp As Long
On Error GoTo eHandle
lngTemp = UBound(arrInput)
IS_ARRAY_EMPTY = False
Exit Function
eHandle:
IS_ARRAY_EMPTY = True
End Function
You specifically mentioned problems in which the calling code needs to iterate over the return value and that iterating over a non-dimensioned array throws an error. Collections don't have that problem. One possibility would be to refactor your code so that it returns a collection (which might or might not have zero elements):
Private Function ParseDates(dates() As String) As Collection
Dim res As New Collection
Dim i As Long
For i = LBound(dates) To UBound(dates)
res.Add #1/1/2000#
Next i
Set ParseDates = res
End Function
Say the calling code has the line:
Set C = ParseDates(dates)
Even if C.Count = 0, the following loop works:
Dim d As Variant
For Each d In C
'process d
Next d
Try this:
Private Function ParseDates(dates() As String) As Date()
Dim res() As Date
Dim i As Integer
Dim k%
k=0
If UBound(dates) >= 0 Then
ReDim res(UBound(dates)) As Date
End If
For i = LBound(dates) To UBound(dates)
if dates(i)<>"" then
k=k+1
redim preserve res(k)
end if
res(k) = #01/01/2000#
Next i
if k=0 then
redim res(ubound(dates))
end if
ParseDates = res
End Function

How declare non standard array belong to class?

I have a VBA class:
Option Explicit
Public Re As Double 'Real
Public Im As Double 'Imaginary
Public Function CZ_Sqt(Z As Complex, Exp As Integer) As Variant
Dim Table() As Complex
Dim i As Integer
If Exp > 0 Then
ReDim Preserve Table(0 To Exp - 1)
Set Table(UBound(Table)) = New Complex
Else: Exit Function
End If
For i = 0 To UBound(Table)
Table(i).Re = 1
Table(i).Im = 1
Next i
set CZ_Sqt = Table
End Function
In module:
Sub asd()
Dim K As Complex
Dim A As Variant
Set K = New Complex
K.Re = 1
K.Im = 3
Set A = K.CZ_Sqt(Z, 5)
end sub
How "set" all variable "Table" in on step ?
In solution which is in example is set only element Table(4) but other elements are omited.
How to return this variable "Table" to the function name "CZ_Sqt" ?
This what I proposed doesn't work.
How pass variable "CZ_Sqt" which consider Array Complex type to the variable "A"?
You are using the same class as an object and object collection.
I would separate the functionalities into 2 Classes:
Complex
ComplexCollection - Contains a collection of complex class
EDIT: There is no duplicate check in ComplexCollection.Add and exists check in ComplexCollection.Retrieve.
Complex
Option Explicit
Public Re As Double
Public Im As Double
ComplexCollection
Option Explicit
Dim oCol As Collection
Public Function Create(pRe As Double, pIm As Double) As Complex
Dim oResult As Complex
Set oResult = New Complex
oResult.Re = pRe
oResult.Im = pIm
Set Create = oResult
End Function
Public Sub Add(pComplex As Complex, pKey As String)
oCol.Add pComplex, pKey
End Sub
Public Function Retrieve(pKey As String) As Complex
Set Retrieve = oCol(pKey)
End Function
Private Sub Class_Initialize()
Set oCol = New Collection
End Sub
Private Sub Class_Terminate()
Set oCol = Nothing
End Sub
Test.bas
Public Sub TestCollection()
Dim oCL As ComplexCollection
Dim oC As Complex
Set oCL = New ComplexCollection
Set oC = oCL.Create(1, 2)
Debug.Print oC.Im, oC.Re
oCL.Add oC, "1"
Set oC = Nothing
Set oC = oCL.Retrieve("1")
Debug.Print oC.Im, oC.Re
End Sub

Can't assign array using array member variable

I have a class where one of the member variables is an array. I am trying to assign an array to the object but keep getting the 'Can't assign array' compile error. Also I was curious as how to get UBound of the array in object. UBound(obj.array) doesn't compile. I am using excel 07 vba.
'Test routine that keeps failing
Sub test()
Dim Arr(2) As String
Arr(0) = ""
Arr(1) = "Pizza"
Arr(2) = "Hoes"
Dim obj As Cats
Set obj = New Cats
obj.avry = Arr
obj.field = 4
MsgBox UBound(obj.ary)
End Sub
'Class declaration
Private pary() As String
Private pfield As Long
Public Property Get ary(ByVal index As Long) As String
Set ary = pary(index)
End Property
Public Property Let avry(Value() As String)
ReDim pary(UBound(Value)) As String
For i = LBound(Value) To UBound(Value)
pary(i) = Value(i)
Next i
End Property
Public Property Get field() As Long
field = pfield
End Property
Public Property Let field(Value As Long)
pfield = Value
End Property
Private Sub Class_Initialize()
pfield = 0
End Sub
This worked for me
Sub test()
Dim Arr(2) As String
Arr(0) = ""
Arr(1) = "Pizza"
Arr(2) = "Hoes"
Dim obj As Cats
Set obj = New Cats
obj.avry = Arr
obj.field = 4
MsgBox obj.ary(2)
End Sub
Public Property Get ary(ByVal index As Long) As String
ary = pary(index)
End Property
Public Property Let avry(vValue As Variant)
ReDim pary(UBound(vValue)) As String
Dim i As Long
For i = LBound(vValue) To UBound(vValue)
pary(i) = vValue(i)
Next i
End Property
Public Property Get field() As Long
field = pfield
End Property
Public Property Let field(Value As Long)
pfield = Value
End Property
Private Sub Class_Initialize()
pfield = 0
End Sub
As Tim said, you can pass the array as a variant. Your MsgBox is trying to find a UBound of a String data type, so that was a problem. Also, you weren't passing an argument to ary in the MsgBox. The ary property returns a String, but you were using the Set keyword, which was another problem.
There seems to be an issue with passing array parameters to class properties.
You can get around this by switching the Let parameter to a Variant:
Public Property Let avry(ByRef arrVal As Variant)
Dim i As Integer
If IsArray(arrVal) Then
ReDim pary(LBound(arrVal) To UBound(arrVal))
For i = LBound(arrVal) To UBound(arrVal)
pary(i) = arrVal(i)
Next i
End If
End Property

Resources