Return to VBA Code Examples
This tutorial will demonstrate how to create and use arrays of objects in VBA.
In VBA, Arrays are variables that store multiple values. You can store VBA Objects in arrays in the same way as you would store any other data.
Declaring the Object Array
In this example, we will declare an array of VBA worksheets:
Dim arWks(3) As Worksheet
Populating a Static Object Array
Declare the Object Array to be Static, and then you can populate the array with the sheets you select from your workbook.
Sub TestObjArray()
'define the array as a worksheet array
Dim arWks(1 to 3) As Worksheet
'add 3 sheet into the array
set arWks(1) = Sheets(1)
set arWks(2) = Sheets(2)
Set arWks(3) = Sheets(3)
End Sub
Populating a Dynamic Object Array
You can declare the Object Array to be Dynamic, and then count the sheets in the workbook before assigning the array size to the Object Array.
Sub TestObjArray()
'define the array as a worksheet array
Dim arWks() As Worksheet
'count how many worksheets in the file, and re-dim the array
Dim n As Integer
Dim i As Integer
'count the sheets and minus one to set the bounds for the array
n = Application.Sheets.Count - 1
ReDim arWks(n)
'fill the worksheet array with all the sheets in the workbook
For i = LBound(arWks) to UBound(arWks)
Set arWks(i) = ActiveWorkbook.Sheets(i + 1)
Next i
End Sub
In the example above, we first declare the Worksheet Array. We then count the number of sheets in the workbook, and assign that value minus one to the UBound of the Array. This is due to the fact that the LBound of the Array starts as 0. Finally, we loop through the sheets and add each sheet to the array.
Using the Object Array in VBA Code
Once we have populated the Worksheet array, we can use VBA to loop through the array.
Sub TestObjArray()
'define the array as a worksheet array
Dim arWks() As Worksheet
'count how many worksheets in the file, and re-dim the array
Dim n As Integer
Dim i As Integer
'count the sheets and minus one to set the bounds for the array
n = Application.Sheets.Count - 1
ReDim arWks(n)
'fill the worksheet array with all the sheets in the workbook
For i = LBound(arWks) to UBound(arWks)
Set arWks(i) = ActiveWorkbook.Sheets(i + 1)
Next i
'do something to every sheet in the array
For i = LBound(arWks) to UBound(arWks)
arWks(i).Range("A1:H1").Font.Bold = True
Next i
End Sub
In the example above, we loop through the array and Bold the first row of each sheet in the array.
VBA Coding Made Easy
Stop searching for VBA code online. Learn more about AutoMacro — A VBA Code Builder that allows beginners to code procedures from scratch with minimal coding knowledge and with many time-saving features for all users!
Learn More!
“I’m not a builder of buildings, I’m a builder of collections” – Leonard Lauder
A Quick Guide to Collections
Task | Examples |
---|---|
Declare | Dim coll As Collection |
Create at run time | Set coll = New Collection |
Declare and Create | Dim coll As New Collection |
Add item | coll.Add «Apple» |
Access item | coll(1) or coll(2) |
Access item added first | coll(1) |
Access item added last | coll(coll.Count) |
Get number of items | coll.Count |
Access all items(For) | Dim i As Long For i = 1 To coll.Count Debug.Print coll(i) Next i |
Access all items(For Each) | Dim fruit As Variant For Each fruit In coll Debug.Print fruit Next fruit |
Remove item | coll.Remove(1) |
Remove all Items | Set coll = New Collection |
Introduction
Collections are a very important part of VBA. If you have used the language for any length of time then you will have used Collections. The most common ones are the Workbooks, Worksheets, Range and Cells collections.
The following code shows some examples of using the VBA Workbooks collection:
' Workbooks is a collection of all open workbooks ' Count is the number of workbooks in the collection Debug.Print Workbooks.Count ' Print the full name of the workbook called Example.xlsm Debug.Print Workbooks("Example.xlsm").FullName ' Print the full name of the workbook that was opened second Debug.Print Workbooks(2).FullName
Collections are similar to arrays so it is important to understand what they are and how the differ to arrays.
Download the Code
Collections Webinar
If you are a member of the website, click on the image below to view the webinar.
(Note: Website members have access to the full webinar archive.)
What is a Collection?
Collections and arrays are both used to group variables. They both store a set of similar items e.g. a list of student marks or country names. Using a collection or array allows you to quickly and easily manipulate a large number of items.
In my post on arrays, I explained in simple terms what arrays are and why they are so useful. I will briefly recap this information here.
If you were storing the marks of one student then you can easily do this using a single variable
Dim mark As Long mark = sheetMarks.Range("A1")
However most of the time you will have more than one student to deal with. Imagine you want to store the marks of 100 students. If you didn’t use collections or arrays you would need to create a hundred variables – one variable to store the mark for each student.
Another problem is that you have to use these variables individually. If you want to store 100 marks then you need a line of code each time you want to store a value to a variable.
' Declare a variable for each mark Dim mark1 As Long Dim mark2 As Long . . . Dim mark100 As Long ' Store the marks from the worksheet in a variable mark1 = sheetMarks.Range("A1") mark2 = sheetMarks.Range("A2") . . . mark100 = sheetMarks.Range("A100")
As you can see in the above example, writing code like this would mean hundreds of lines of repetitive code. When you use a collection or array you only need to declare one variable. Using a loop with a collection or with arrays means you only need one line for add or reading values.
If we rewrite the above example using a collection then we only need a few lines of code:
' Create collection Dim collMarks As New Collection ' Read 100 values to collection Dim c As Range For Each c In Sheet1.Range("A1:A100") ' This line is used to add all the values collMarks.Add c.Value Next
Collections Vs Arrays?
We have looked at what collections and arrays have in common. So what is the difference and why use one over the other?
The main difference is that with an array you normally set the size once. This means that you know the size before you start adding elements. Let me explain this with an example.
Example: Where an Array is Better
Imagine you have a worksheet of student marks with one student per row:
Student Marks
You want to store information about each student. In this example, you can easily count the number of rows to get the number of students. In other words, you know the number of items in advance.
In the example code you can see that we get the number of students by counting the rows. We can then use this to create an array of the correct size:
' Get last row - this is the number of students Dim lStudentCount As Long lStudentCount = Sheet1.Range("A" & Rows.Count).End(xlUp).Row ' Create array of correct size Dim arr() As Long ReDim arr(1 To lStudentCount)
Let us now look at a second example where we don’t know the number of items in advance
Example Where a Collection is Better
In this example we have the same student worksheet but this time we only want students with a given criteria. For example, only the students from the USA or England that study Maths or History. In other words, you will not know how to select a student until you read their details from the worksheet.
Imagine also that students can be added or removed from the list as the application runs.
So in this example the number of students is not fixed and changes a lot. Here you do not know the number of students in advance. Therefore you do not know what size array to create.
You could create an array of the biggest possible size. The problem is you would have a lot of empty slots and would have to add code to deal with these. If you read 50 students from a max of 1000 then you would have 950 unused array slots.
You could also resize the array for each item as it is added. This is very inefficient and quite messy to do.
So for this example using a collection would be better.
' Declare Dim coll As New Collection ' Add item - VBA looks after resizing coll.Add "Apple" coll.Add "Pear" ' remove item - VBA looks after resizing coll.Remove 1
When you add or remove an item to a collection VBA does all the resizing for you. You don’t have to specify the size or allocate new spaces. VBA does it under the hood. All you have to do is add an item or remove it.
Another Advantage of Collections
Collections are much easier to use than arrays especially if you are new to programming. Most of the time you do three things with collections:
- Create the collection
- Add some items
- Read through the items
So if you are not dealing with a larger number of items then using a Collection can be much neater to use.
A Disadvantage of Collections
Basic data types(i.e. variables like string, date, long, currency etc.) in a Collections are read-only.You can add or remove an item but you cannot change the value of the item. If you are going to be changing the values in a group of items then you will need to use an array.
If you are storing an object in a Collection then you can change the value as the Collection stores a reference rather than the actual object.
Now that we know when and why to use a collection let’s look at how to use one.
You can learn more about Collection vs Arrays in this video…
How to Create a Collection
You can declare and create in one line as the following code does
' Declare and create Dim coll As New Collection
As you can see you don’t need to specify the size. Once your collection has been created you can easily add items to it.
You can also declare and then create the collection if and when you need it.
' Declare Dim coll As Collection ' Create Collection Set coll = New Collection
Minor Difference Between These Methods
The difference between these methods is that for the first one the collection is always created. For the second method the collection is only created when the Set line is reached. So you could set the code to only create the collection if a certain condition was met
' Declare Dim coll As Collection ' Create Collection if a file is found If filefound = True Then Set coll = New Collection Endif
The advantage to using this method is minimal. Allocating memory was important back in the 1990’s when computer memory was limited. Unless you are creating a huge number of collections on a slow PC you will never notice any benefit.
Use Set means the collection will behave differently than when you set the collection to nothing. The next section explains this.
Check out the video below to learn more about Collections…
Removing All items from a Collection
The Collection does not have a RemoveAll function. However to remove all items from a collection you can simply set it to a new collection:
Set Coll = New Collection.
VBA will delete the collection because we are no longer referencing it. When we remove all items we generally want to use the collection again so we are effectively killing two birds with one stone by using this method:
' https://excelmacromastery.com/ Sub DeleteCollection() Dim coll1 As New Collection coll1.Add "apple" coll1.Add "pear" ' The original collection is deleted Set coll1 = New Collection End Sub
One subtle thing to keep in mind is that if we have two or more variables which reference the same collection then it will not be deleted(see cleaning up memory in VBA).
In the example below the original collection items are not deleted because it is still referenced by coll2
' https://excelmacromastery.com/ Sub CollectionNotDeleted() Dim coll1 As New Collection, coll2 As Collection coll1.Add "apple" coll1.Add "pear" ' Coll1 and Coll2 both reference the collection Set coll2 = coll1 ' Coll1 now references a new collection Set coll1 = New Collection ' Coll2 refers to the original collection - prints apple Debug.Print coll2(1) End Sub
Adding items to a Collection
It is simple to add items to a collection. You use the add property followed by the value you wish to add
collFruit.Add "Apple" collFruit.Add "Pear"
You can have any basic type in a collection such as a Double
collTotals.Add 45.67 collTotals.Add 34.67
When you add items in this manner they are added to the next available index. In the fruit example, Apple is added to position 1 and Pear to position 2.
Before and After
You can use the Before or After parameters to specify where you want to place the item in the collection. Note you cannot use both of these arguments at the same time.
collFruit.Add "Apple" collFruit.Add "Pear" ' Add lemon before first item collFruit.Add "Lemon", Before:=1
After this code the collection is in the order:
1. Lemon
2. Apple
3. Pear
collFruit.Add "Apple" collFruit.Add "Pear" ' Add lemon after first item collFruit.Add "Lemon", After:=1
After this code the collection is in the order:
1. Apple
2. Lemon
3. Pear
Accessing Items of a Collection
To Access the items of a collection you simply use the index. As we saw the index is the position of the item in the collection based on the order they were added.
The order can also be set using the Before or After parameter.
' https://excelmacromastery.com/ Sub access() Dim coll As New Collection coll.Add "Apple" coll.Add "Pear" ' Will print Apple Debug.Print coll(1) ' Add orange first coll.Add "Orange", Before:=1 ' Will print Orange Debug.Print coll(1) ' Will print Apple as it is now in position 2 Debug.Print coll(2) End Sub
You can also use the Item Property to access an item in the collection. It is the default method of the collection so the following lines of code are equivalent:
Debug.Print coll(1) Debug.Print coll.Item(1)
Are Items in a Collection Read-Only?
This is a very important point. When a basic data type is stored in a Collection it is read-only. A basic data type is a string, date, integer, long etc.
If you try to update a Collection item you will get an error. The following code produces an “object required” error:
' https://excelmacromastery.com/ Sub WriteValue() Dim coll As New Collection coll.Add "Apple" ' This line causes an ERRROR coll(1) = "Pear" End Sub
However you can change the class object that is stored in a Collection:
' Demonstrates that a class object can be updated in a collection. ' https://excelmacromastery.com/excel-vba-collections/ Sub ChangeObject() Dim coll As New Collection Dim o As New Class1 ' set value of fruit member of the class o.fruit = "Apple" ' Add object to collection coll.Add o ' Prints Apple Debug.Print "Object fruit is " & coll(1).fruit ' Change the fruit part of class1 object coll(1).fruit = "Pear" ' Prints pear Debug.Print "Object fruit is " & coll(1).fruit End Sub
This may seem like contradictory behaviour, but there is a good reason. Any item that is added to a Collection is read-only. However, when you add an object to a Collection, the object is not added as the item. A variable with the memory address of the object is added as the item.
This happens seamlessly so that you don’t notice it. The item variable is actually read-only but the object it points to is not.
All you need to remember is that basic data types in a Collection are read-only. Objects in a Collection can be changed.
You can read more about objects in memory here.
Adding different types
You can also add different types of items to a collection.
collFruit.Add "Apple"
collFruit.Add 45
collFruit.Add #12/12/2017#
This is seldom needed. In VBA the Sheets collections contains sheets of type Worksheet and of type Chart. (To create a Chart sheet simple right click on any Chart, select Move and select the radio button for New sheet).
The following code displays the type and name of all the sheets in the current workbook. Note to access different type you need the For Each variable to be a variant or you will get an error:
' https://excelmacromastery.com/ Sub ListSheets() Dim sh As Variant For Each sh In ThisWorkbook.Sheets ' Display type and name of sheet Debug.Print TypeName(sh), sh.Name Next End Sub
When you access different items the For Each variable must be a variant. If it’s not you will get an error when you access a different type than you declared. If we declared sh as a worksheet in the above example it would give an error when we try to access a sheet of type Chart.
It is rare that you would need a collection of different types but as you can see sometimes it can be useful.
Adding Items Using a Key
You can also add items using a key as the next example shows:
collMark.Add Item:=45, Key:="Bill" Debug.Print "Bill's Marks are: ",collMark("Bill")
I included the parameter names to make the above example clear. However you don’t need to do this. Just remember the key is the second parameter and must be a unique string.
The following code shows a second example of using keys:
' https://excelmacromastery.com/ Sub UseKey() Dim collMark As New Collection collMark.Add 45, "Bill" collMark.Add 67, "Hank" collMark.Add 12, "Laura" collMark.Add 89, "Betty" ' Print Betty's marks Debug.Print collMark("Betty") ' Print Bill's marks Debug.Print collMark("Bill") End Sub
Using keys is has three advantages:
- If the order changes your code will still access the correct item
- You can directly access the item without reading through the entire collection
- It can make you code more readable
In the VBA Workbooks collection it is much better to access the workbook by the key(name) than by the index. The order is dependent on the order they were opened and so is quite random:
' https://excelmacromastery.com/ Sub UseAWorkbook() Debug.Print Workbooks("Example.xlsm").Name Debug.Print Workbooks(1).Name End Sub
When to Use Keys
An example of when to use keys is as follows: Imagine you have a collection of IDs for a 10,000 students along with their marks.
You also have a number of worksheet reports that have lists of student IDs. For each of these worksheets you need to print the mark for each student.
You could do this by adding the 10,000 students to a collection using their student id as they key. When you read an ID from the worksheet you can directly access this student’s marks.
If you didn’t use a key you would have to search through 10,000 IDs for each ID on the report.
The Shortcoming of Using Keys in Collections
There are two shortcomings of keys in Collections
- You cannot check if the Key exists.
- You cannot update the value stored at the Key unless it is an object.
The first issue is easy to get around. The following code checks if a key exists
' https://excelmacromastery.com/ Function Exists(coll As Collection, key As String) As Boolean On Error Goto EH IsObject (coll.Item(key)) Exists = True EH: End Function
You can use it like this:
' https://excelmacromastery.com/ Sub TestExists() Dim coll As New Collection coll.Add Item:=5, key:="Apple" coll.Add Item:=8, key:="Pear" ' Prints true Debug.Print Exists(coll, "Apple") ' Prints false Debug.Print Exists(coll, "Orange") ' Prints true Debug.Print Exists(coll, "Pear") End Sub
The second issue is that it is not possible to update a value in a Collection. However, we can update an object and the reason for this is that the collection doesn’t actually store the object. It stores the address of the object.
If you need to update a basic value like a long, string etc. then it’s not possible. You have to remove the item and add a new one.
If you wish to use keys there is an alternative to the Collection. You can use the Dictionary. The Dictionary provides more functionality to work with keys. You can check if keys exist, update the values at keys, get a list of the keys and so on.
Check out this video to see more about Dictionary vs Collection…
Check if an item exists in a Collection
There is no built-in function for checking if an item exists in the collection. One way to do it is to read through all the items in a collection and check them individually. This is very inefficient particularly if you’re dealing with a large dataset:
' This method is slow but works if multiple items Function Exists(coll As Collection, checkItem As String) As Boolean Dim item As Variant For Each item In coll If item = checkItem Then Exists = True Exit For End If Next item End Function
If the values in the collection are unique then we can add the item as a key and use the function below to see if the key exists:
' https://excelmacromastery.com/ Function Exists(coll As Collection, key As String) As Boolean On Error Goto EH IsObject (coll.Item(key)) Exists = True EH: End Function
Sub TestExists() Dim coll As New Collection ' Add items using parameter names to make it clearer coll.Add Item:="Apple", key:="Apple" ' Works the same without the parameter names coll.Add "Orange", "Orange" Debug.Print "Apple exists is " & Exists(coll, "Apple") Debug.Print "Pear exists is " & Exists(coll, "Pear") End Sub
Accessing all items in a Collection
To access all the items in a collection you can use a For loop or a For Each loop. Let’s look at these individually.
Using the For Loop
With a normal For Loop, you use the index to access each item. The following example prints the name of all the open workbooks
' https://excelmacromastery.com/ Sub AllWorkbook() Dim i As Long For i = 1 To Workbooks.Count Debug.Print Workbooks(i).Name Next i End Sub
You can see that we use the range of 1 to Workbooks.Count. The first item is always in position one and the last item is always in the position specified by the Count property of the collection.
The next example prints out all the items in a user-created collection:
' https://excelmacromastery.com/ Sub UserCollection() ' Declare and Create collection Dim collFruit As New Collection ' Add items collFruit.Add "Apple" collFruit.Add "Pear" collFruit.Add "Plum" ' Print all items Dim i As Long For i = 1 To collFruit.Count Debug.Print collFruit(i) Next i End Sub
Using the For Each
The For Each loop that is a specialised loop the is used for Collections. It doesn’t use the index and the format is shown in the following example:
' https://excelmacromastery.com/ Sub AllWorkbookForEach() Dim book As Variant For Each book In Workbooks Debug.Print book.Name Next End Sub
The format of the For loop is:
For i = 1 To Coll.Count
Next
where i is a long and Coll is a collection.
The format of the For Each Loop is:
For Each var In Coll
Next
where var is a variant and Coll is a collection.
To access each the item
For: Coll(i)
For Each: Var
The following example shows the loops side by side for the above user collection example:
' https://excelmacromastery.com/ Sub UseBothLoops() ' Declare and Create collection Dim collFruit As New Collection ' Add items collFruit.Add "Apple" collFruit.Add "Pear" collFruit.Add "Plum" ' Print all items using For Dim i As Long For i = 1 To collFruit.Count Debug.Print collFruit(i) Next i ' Print all items using For Each Dim fruit As Variant For Each fruit In collFruit Debug.Print fruit Next fruit End Sub
For Each Versus For
It is important to understand the difference between the two loops.
The For Each Loop
- is faster
- is neater to write
- has one order only – low index to high
The For Loop
- is slower
- is less neater to write
- can access in different order
Let’s compare the loops under each of these attributes
Speed
The For Each is considered faster than the For Loop. Nowadays this is only an issue if you have a large collection and/or a slow PC/Network.
Neater
The For Each loop is neater to write especially if you are using nested loops. Compare the following loops. Both print the names of all the worksheets in open workbooks.
' https://excelmacromastery.com/ Sub PrintNamesFor() ' Print worksheets names from all open workbooks Dim i As Long, j As Long For i = 1 To Workbooks.Count For j = 1 To Workbooks(i).Worksheets.Count Debug.Print Workbooks(i).Name, Workbooks(i).Worksheets(j).Name Next j Next i End Sub Sub PrintNamesForEach() ' Print worksheets names from all open workbooks Dim bk As Workbook, sh As Worksheet For Each bk In Workbooks For Each sh In bk.Worksheets Debug.Print bk.Name, sh.Name Next sh Next bk End Sub
The For Each loop is much neater to write and less likely to have errors.
Order
The order of the For Each loop is always from the lowest index to the highest. If you want to get a different order then you need to use the For Loop. The order of the For Loop can be changed. You can read the items in reverse. You can read a section of the items or you can read every second item:
' Read through the worksheets in different orders ' Note: You need a least 3 worksheets in the workbook or ' you will get an error. ' https://excelmacromastery.com/excel-vba-collections/ Sub ReadRightToLeft() ' Go through sheets from right to left Debug.Print vbNewLine & "Sheets in reverse" Dim i As Long For i = ThisWorkbook.Worksheets.Count To 1 Step -1 Debug.Print ThisWorkbook.Worksheets(i).Name Next i ' Read the names of the first 2 sheets Debug.Print vbNewLine & "First two sheets " For i = 1 To 2 Debug.Print ThisWorkbook.Worksheets(i).Name Next i ' Go through every second sheet Debug.Print vbNewLine & "Every second sheet" For i = 1 To ThisWorkbook.Worksheets.Count Step 2 Debug.Print ThisWorkbook.Worksheets(i).Name Next i End Sub
The For loop gives more flexibility here but the reality is that most of the time the basic order is all you need.
You can find more about For Loops in the below video…
Sorting a Collection
There is no built-in sort for the VBA collection. However we can use this QuickSort
' https://excelmacromastery.com/ Sub QuickSort(coll As Collection, first As Long, last As Long) Dim vCentreVal As Variant, vTemp As Variant Dim lTempLow As Long Dim lTempHi As Long lTempLow = first lTempHi = last vCentreVal = coll((first + last) 2) Do While lTempLow <= lTempHi Do While coll(lTempLow) < vCentreVal And lTempLow < last lTempLow = lTempLow + 1 Loop Do While vCentreVal < coll(lTempHi) And lTempHi > first lTempHi = lTempHi - 1 Loop If lTempLow <= lTempHi Then ' Swap values vTemp = coll(lTempLow) coll.Add coll(lTempHi), After:=lTempLow coll.Remove lTempLow coll.Add vTemp, Before:=lTempHi coll.Remove lTempHi + 1 ' Move to next positions lTempLow = lTempLow + 1 lTempHi = lTempHi - 1 End If Loop If first < lTempHi Then QuickSort coll, first, lTempHi If lTempLow < last Then QuickSort coll, lTempLow, last End Sub
You can use it like this:
' https://excelmacromastery.com/ Sub TestSort() Dim coll As New Collection coll.Add "USA" coll.Add "Spain" coll.Add "Belguim" coll.Add "Ireland" QuickSort coll, 1, coll.Count Dim v As Variant For Each v In coll Debug.Print v Next End Sub
Using Collections with Functions and Subs
Using a Collection as a parameter or return value is very easy to do. We will look at them in turn.
Passing a Collection to a Sub/Function
It is simple to pass a collection to a function or sub. It is passed like any parameter as the following code example shows:
' https://excelmacromastery.com/ Sub UseColl() ' Create collection Dim coll As New Collection ' Add items coll.Add "Apple" coll.Add "Orange" ' Pass to sub PrintColl coll End Sub ' Sub takes collection as argument Sub PrintColl(ByRef coll As Collection) Dim item As Variant For Each item In coll Debug.Print item Next End Sub
You can see how useful the sub PrintColl is in the example. It will print all the elements of ANY collection. The size or type of element does not matter. This shows how flexible collections are to use.
Passing ByVal versus ByRef
One subtle point to keep in mind here is passing by value(By Val) and passing by reference(ByRef) differ slightly.
For a simple variable passing by value means a copy is created. This means if the Function/Sub changes the value will not be changed when you return to the calling procedure.
In the following example, we pass total using both ByVal and ByRef. You can see that after we pass using ByRef the value has changed in the calling procedure:
' https://excelmacromastery.com/ Sub PassType() Dim total As Long total = 100 PassByValue total ' Prints 100 Debug.Print total PassByReference total ' Prints 555 Debug.Print total End Sub Sub PassByValue(ByVal total As Long) ' value changed only in this sub total = 555 End Sub Sub PassByReference(ByRef total As Long) ' value also changed outside this sub total = 555 End Sub
Using ByVal and ByRef with a Collection is a bit different. If you add or remove item then the collection in the original caller will also be changed. So the Subs in the following example will both remove the first item of the original collection:
' https://excelmacromastery.com/ Sub RemoveByRef(ByRef coll As Collection) coll.Remove 1 End Sub Sub RemoveByVal(ByVal coll As Collection) coll.Remove 1 End Sub
The reason for this is that a Collection variable contains a pointer. This means it contains the address of the collection rather than the actual collection. So when you add or remove an item you are changing what the pointer is pointing at and not the pointer itself. However if you change the pointer it will be changed outside of the sub.
You don’t need to worry about pointers. All you need to know is how this affects the behaviour of passing a parameter. If you set a collection parameter to nothing then the behaviour depends on if you used ByRef or ByVal:
- Using ByRef will reset the original collection
- Using ByVal will not change the original collection
' https://excelmacromastery.com/ ' Will empty original collection Sub PassByRef(ByRef coll As Collection) Set coll = Nothing End Sub ' Will NOT empty original collection Sub PassByVal(ByVal coll As Collection) Set coll = Nothing End Sub
Returning a Collection From a Function
Returning a collection from a Function is the same as returning any object. You need to use the Set keyword. In the following example you can see how to return a collection
' https://excelmacromastery.com/ Sub FruitReport() ' NOTE: We do not use New keyword here to create the collection. ' The collection is created in the CreateCollection function. Dim coll As Collection ' receive coll from the CreateCollection function Set coll = CreateCollection ' do something with coll here End Sub Function CreateCollection() As Collection Dim coll As New Collection coll.Add "Plum" coll.Add "Pear" ' Return collection Set CreateCollection = coll End Function
Note: that you don’t use the New keyword when declaring the collection in the sub FruitReport(). This is because the collection is created in CreateCollection(). When you return the collection you are simple assigning the collection variable to point to this collection.
Example – Reading from a worksheet
Let’s have a look at an example of using the Collection with a worksheet.
We use this data:
Student Marks
The user selects the subject in cell B2:
In this example we read through the list of students. Any student, that is taking the subject listed in cell B2, is added to the collection.
We then write the results to the worksheet from cell F5 onwards.
You can download this example below as part of the source code for this post.
The code is here:
' The user selects a subject. ' Read through the students and add the ones with that subject ' to the collection. ' Then write out the collection to the worksheet. ' https://excelmacromastery.com/excel-vba-collections/ Public Sub CreateReport() ' Get the range from the table Dim rg As Range Set rg = shStudents.ListObjects("tbStudents").DataBodyRange Dim coll As New Collection ' Read through the students Dim i As Long For i = 1 To rg.Rows.Count ' If the student has the selected 'subject' then add to the collection If rg.Cells(i, 3).Value = shStudents.Range("B2").Value Then coll.Add rg.Cells(i, 1).Value End If Next i ' clear existing data below the header shStudents.Range("F4").CurrentRegion.Offset(1).ClearContents ' Write the collection to the worksheet Dim item As Variant, currentRow As Long currentRow = 5 For Each item In coll ' Write the item to the worksheet shStudents.Cells(currentRow, 6).Value = item ' Move to the next row currentRow = currentRow + 1 Next item End Sub
You can watch the video below to see more about reading between Collections and Worksheets…
Conclusion
Collections are a very useful part of VBA. There are much easier to use than Arrays and are very useful when you are doing a lot of adding and removing items. They have only four properties: Add, Remove, Count and Item. This makes them very easy to master.
The main points of this post are:
- Collections are a way of storing a group of items together.
- VBA has its own collections such as Workbooks, Worksheets and Cells.
- The items do not have to be of the same type but they normally are. The VBA Sheets collection can contain both worksheets and chart sheets.
- A collection makes it easy to perform the same task on multiple items e.g. print all the values.
- Collections are similar to arrays as they both store groups of similar items.
- Collections are better when adding and removing lots of items.
- Collections are simpler to use than arrays.
- Arrays are more useful when the number of items is fixed.
- Arrays are more efficient when reading and writing to or from cells.
- Basic data types(i.e. non-objects) in a Collection are read-only whereas arrays are read/write.
- You can create a collection using Dim only or Dim with Set
- You can delete an entire collection by setting it to Nothing. What this does depends on how it was created(see last point).
- You can add items to a specific position in the collection using Before and After arguments with the collection Add function.
- You can use Keys with a collection to access an item directly. Collections do not have good support for keys so you are usually better to use the Dictionary collection when you need to use Keys.
- You can use the For and For Each loops to access all items in a collection. The For Each loop is more efficient but only allows you to go through the collection in one order.
- You can easily pass a collection as an argument to a Function or Sub.
- You can easily return a collection from a Function.
What’s Next?
Free VBA Tutorial If you are new to VBA or you want to sharpen your existing VBA skills then why not try out the The Ultimate VBA Tutorial.
Related Training: Get full access to the Excel VBA training webinars and all the tutorials.
(NOTE: Planning to build or manage a VBA Application? Learn how to build 10 Excel VBA applications from scratch.).
МЕНЮ САЙТА
КАТЕГОРИИ РАЗДЕЛА ОПРОСЫ |
Работа с коллекциями классов
Добавлять комментарии могут только зарегистрированные пользователи. [ Регистрация | Вход ] |
На чтение 24 мин. Просмотров 33.6k.
Леонард Лаудер
Я не строитель зданий, я строитель коллекций
Содержание
- Краткое руководство по коллекциям
- Введение
- Что такое коллекция?
- Коллекции против Массивов
- Как создать коллекцию
- Удаление всех элементов из коллекции
- Добавление предметов в коллекцию
- Доступ к элементам коллекции
- Добавление разных типов
- Добавление элементов с помощью ключа
- Доступ ко всем элементам в коллекции
- Сортировка коллекции
- Использование коллекций с функциями и подпрограммами
- Заключение
Краткое руководство по коллекциям
Задача | Пример |
Объявить | Dim coll As Collection |
Создать во время выполнения | Set coll = New Collection |
Объявить и создать | Dim coll As New Collection |
Добавить элемент | coll.Add «Яблоко» |
Доступ к элементу | coll(1) or coll(2) |
Элемент доступа добавлен в первый раз |
coll(1) |
Доступ к элементу добавлен в последний раз |
coll(coll.Count) |
Получить количество предметов | coll.Count |
Доступ ко всем предметам | Dim i As Long For i = 1 To coll.Count Debug.Print coll(i) Next i |
Доступ ко всем предметам (For Each) |
Dim fruit As Variant For Each fruit In coll Debug.Print fruit Next fruit |
Удалить элемент | coll.Remove(1) |
Удалить все элементы | Set coll = New Collection |
Введение
Коллекции являются очень важной частью VBA. Если вы пользовались VBA какое-то время, вы будете использовать Коллекции. Наиболее распространенными из них являются
Workbooks, Worksheets, коллекции Range и Cells.
В следующем коде показаны некоторые примеры использования коллекции Workbooks VBA.
' Workbooks это коллекция всех открытых рабочих книг ' Count - это количество книг в коллекции. Debug.Print Workbooks.Count ' Напечатайте полное имя книги с именем Пример.xlsm Debug.Print Workbooks("Пример.xlsm").FullName ' Напечатайте полное название книги, которая была открыта второй Debug.Print Workbooks(2).FullName
Коллекции похожи на массивы, поэтому важно понимать, что они из себя представляют и чем они отличаются от массивов.
Что такое коллекция?
Коллекции и массивы используются для группировки переменных. Они оба хранят набор похожих предметов, например список студенческих оценок или названий стран. Использование коллекции или массива позволяет быстро и легко манипулировать большим количеством предметов.
В своей статье о массивах я объяснил простым языком, что такое массивы и почему они так полезны. Я кратко изложу эту информацию здесь.
Если вы сохраняете оценки одного ученика, вы можете легко сделать это, используя одну переменную.
Dim mark As Long mark = sheetMarks.Range("A1")
Однако в большинстве случаев вам придется иметь дело с несколькими студентами. Представьте, что вы хотите хранить оценки 100 учеников. Если вы не использовали коллекции или массивы, вам нужно было бы создать сотню переменных — одну переменную для хранения оценки для каждого учащегося.
Другая проблема заключается в том, что вы должны использовать эти переменные по отдельности. Если вы хотите сохранить 100 отметок, вам понадобится строка кода каждый раз, когда вы хотите сохранить значение в переменной.
' Объявите переменную для каждой оценки Dim mark1 As Long Dim mark2 As Long . . . Dim mark100 As Long ' Сохраните отметки на листе в переменной mark1 = sheetMarks.Range("A1") mark2 = sheetMarks.Range("A2") . . . mark100 = sheetMarks.Range("A100")
Как вы можете видеть в приведенном выше примере, написание такого кода означало бы сотни строк повторяющегося кода. Когда вы используете коллекцию или массив, вам нужно только объявить одну переменную. Использование цикла с коллекцией или массивами означает, что вам нужна только одна строка для добавления или чтения значений.
Если мы переписываем приведенный выше пример с использованием коллекции, нам нужно всего несколько строк кода
' Создать коллекцию Dim collMarks As New Collection ' Прочитайте 100 значений в коллекцию Dim c As Range For Each c In Sheet1.Range("A1:A100") ' Эта строка используется для добавления всех значений collMarks.Add c.Value Next
Коллекции против Массивов
Мы рассмотрели, что общего у коллекций и массивов. Так в чем же разница и зачем использовать один вместо другого?
Основное отличие состоит в том, что с массивом вы обычно устанавливаете размер один раз. Это означает, что вы знаете размер до того, как начнете добавлять элементы. Позвольте мне объяснить это на примере.
Пример: когда Массив лучше
Представьте, что у вас есть лист оценок учеников с одним учеником на строку:
Вы хотите хранить информацию о каждом ученике. В этом примере вы можете легко посчитать количество строк, чтобы получить количество студентов. Другими словами, вы знаете количество предметов заранее.
' Получить последнюю строку - это количество студентов Dim lStudentCount As Long lStudentCount = Sheet1.Range("A" & Rows.Count).End(xlUp).Row ' Создать массив правильного размера Dim arr() As Long ReDim arr(1 To lStudentCount)
В примере кода, как видите, мы получаем количество студентов путем подсчета строк. Затем мы можем использовать это для создания массива правильного размера.
Массивы используются, когда размер фиксирован.
Давайте теперь посмотрим на второй пример, где мы не знаем количество предметов заранее.
Пример: когда Коллекция лучше
В этом примере у нас есть один и тот же рабочий лист студента, но на этот раз нам нужны только студенты с заданными критериями. Например, только студенты из Твери или Москвы, которые изучают математику или историю. Другими словами, вы не будете знать, как выбрать ученика, пока не прочитаете его данные на рабочем листе.
Представьте также, что студенты могут быть добавлены или удалены из списка во время работы приложения.
Таким образом, в этом примере количество студентов не является фиксированным и сильно меняется. Здесь вы не знаете количество студентов заранее. Поэтому вы не знаете, какой размер массива создать.
Вы можете создать массив максимально возможного размера. Проблема в том, что у вас будет много пустых слотов, и вам придется добавить код, чтобы справиться с ними. Если вы прочитаете 50 учеников с максимумом 1000, то у вас будет 950 неиспользуемых слотов массива.
Вы также можете изменить размер массива для каждого элемента по мере его добавления. Это очень неэффективно и довольно грязно.
Так что для этого примера лучше использовать коллекцию.
' Объявить Dim coll As New Collection ' Добавить элемент - VBA следит за изменением размера coll.Add "Яблоко" coll.Add "Слива" ' удалить элемент - VBA следит за изменением размера coll.Remove 1
Когда вы добавляете или удаляете элемент в коллекцию, VBA выполняет все изменения размера за вас. Вам не нужно указывать размер или выделять новые пробелы. VBA делает это самостоятельно. Все, что вам нужно сделать, это добавить элемент или удалить его.
Коллекция используется, когда размер часто изменяется.
Еще одно преимущество коллекций
Коллекции гораздо проще использовать, чем массивы, особенно если вы новичок в программировании. Большую часть времени вы делаете три вещи с коллекциями:
- Создать коллекцию
- Добавьте несколько предметов
- Прочитайте предмет
Так что, если вы не имеете дело с большим количеством предметов, тогда использование Коллекции может быть намного удобнее в использовании.
Недостаток коллекций
Основные типы данных (т.е. переменные, такие как string, date, long, currency и т.д.) в коллекциях доступны только для чтения. Вы можете добавить или удалить элемент, но не можете изменить его значение. Если вы собираетесь изменять значения в группе элементов, вам нужно будет использовать массив.
Если вы храните объект в коллекции, вы можете изменить значение, так как коллекция хранит ссылку, а не фактический объект.
Коллекция только для чтения
Теперь, когда мы знаем, когда и зачем использовать коллекцию, давайте посмотрим, как ее использовать.
Как создать коллекцию
Вы можете объявить и создать в одной строке, как это делает следующий код:
' Объявить и создать Dim coll As New Collection
Как видите, вам не нужно указывать размер. После того, как ваша коллекция создана, вы можете легко добавлять в нее элементы.
Вы также можете объявить и затем создать коллекцию, если и когда вам это нужно.
' Объявить Dim coll As Collection ' Создать Коллекцию Set coll = New Collection
Незначительная разница между этими методами
Разница между этими методами заключается в том, что для первого всегда создается коллекция. Для второго метода коллекция создается только при достижении строки Set. Таким образом, вы можете установить код для создания коллекции только при соблюдении определенного условия.
' Объявить Dim coll As Collection ' Создать коллекцию, если файл найден If filefound = True Then Set coll = New Collection Endif
Преимущество использования этого метода минимально. Выделение памяти было важно еще в 1990-х годах, когда память компьютера была ограничена. Если вы не создаете огромное количество коллекций на медленном ПК, вы никогда не заметите никакой выгоды.
Использование Set означает, что коллекция будет вести себя иначе, чем когда вы устанавливаете коллекцию в ничто. Следующий раздел объясняет это.
Удаление всех элементов из коллекции
Коллекция не имеет функции RemoveAll. Однако, чтобы удалить все элементы из коллекции, вы можете просто установить ее в новую коллекцию.
Set Coll = New Collection.
VBA удалит коллекцию, потому что мы больше не ссылаемся на нее. Когда мы удаляем все предметы, мы обычно хотим использовать коллекцию снова, поэтому мы эффективно убиваем двух зайцев одним выстрелом, используя этот метод.
Sub UdalitKollekciyu() Dim coll1 As New Collection coll1.Add "яблоко" coll1.Add "слива" ' Оригинальная коллекция удалена Set coll1 = New Collection End Sub
Следует помнить одну тонкую вещь: если у нас есть две или более переменных, которые ссылаются на одну и ту же коллекцию, она не будет удалена (см. Очистка памяти в VBA).
В приведенном ниже примере исходные элементы коллекции не удаляются, так как он все еще является ссылкой по coll2.
Sub KollekciyaNeUdalyaetsya() Dim coll1 As New Collection, coll2 As Collection coll1.Add "яблоко" coll1.Add "слива" ' Coll1 и Coll2 оба ссылаются на коллекцию Set coll2 = coll1 ' Coll1 теперь ссылается на новую коллекцию Set coll1 = New Collection ' Coll2 относится к оригинальной коллекции - печатает яблоко Debug.Print coll2(1) End Sub
Добавление предметов в коллекцию
Добавить предметы в коллекцию просто. Вы используете свойство add, за которым следует значение, которое вы хотите добавить.
collFruit.Add "Яблоко" collFruit.Add "Слива"
Вы можете иметь любой базовый тип в коллекции, например, Double
collTotals.Add 45.67 collTotals.Add 34.67
Когда вы добавляете элементы таким способом, они добавляются в следующий доступный индекс. В примере с фруктами яблоко добавляется в положение 1, а слива — в положение 2.
Before и After
Вы можете использовать параметры «Before» или «After», чтобы указать, где вы хотите разместить элемент в коллекции. Обратите внимание, что вы не можете использовать оба этих аргумента одновременно.
collFruit.Add "Яблоко" collFruit.Add "Слива" ' Добавить лимон перед первым пунктом collFruit.Add "Лимон", Before:=1
После этого кода порядок коллекции выглядит так:
- Лимон
- Яблоко
- Слива
collFruit.Add "Яблоко" collFruit.Add "Слива" ' Добавьте лимон после первого пункта collFruit.Add "Лимон", After:=1
После этого кода порядок коллекции выглядит так:
- Яблоко
- Лимон
- Слива
Доступ к элементам коллекции
Для доступа к элементам коллекции вы просто используете индекс. Как мы увидели, индекс — это позиция элемента в коллекции на основе порядка, в котором они были добавлены.
Порядок также можно установить с помощью параметра «Before» или «After».
Sub dostup() Dim coll As New Collection coll.Add "Яблоко" coll.Add "Слива" ' Будет напечатано Яблоко Debug.Print coll(1) ' добавьте апельсин в начало coll.Add "Апельсин", Before:=1 ' будет напечатан Апельсин Debug.Print coll(1) ' Будет печатать Яблоко, так как оно сейчас в положении 2 Debug.Print coll(2) End Sub
Вы также можете использовать свойство элемента для доступа к элементу в коллекции. Это метод по умолчанию для коллекции, поэтому следующие строки кода эквивалентны.
Debug.Print coll(1) Debug.Print coll.Item(1)
Элементы в коллекции доступны только для чтения?
Это очень важный момент. Когда базовый тип данных хранится в коллекции, он доступен только для чтения. Базовый тип данных — это строка, дата, целое число, длина и т.д.
Если вы попытаетесь обновить элемент коллекции, вы получите сообщение об ошибке. Следующий код выдает ошибку «Требуется объект».
Sub NapisatZnachenie() Dim coll As New Collection coll.Add "Яблоко" ' Эта строка вызывает ОШИБКУ coll(1) = "Слива" End Sub
Вы можете изменить объект, который хранится в коллекции
Sub IzmenitObekt() Dim coll As New Collection Dim o As New Class1 ' Добавить объект в коллекцию o.fruit = "Яблоко" coll.Add o ' Изменить фруктовую часть Class1 coll(1).fruit = "Слива" ' Печатает Слива Debug.Print coll(1).fruit End Sub
Это может показаться противоречивым поведением, но тому есть веская причина. Любой элемент, добавленный в коллекцию, доступен только для чтения. Однако при добавлении объекта в коллекцию объект не добавляется как элемент. Переменная с адресом памяти объекта добавляется в качестве элемента.
Это происходит незаметно, чтобы вы этого не заметили. Переменная item на самом деле доступна только для чтения, но объект, на который она указывает, — нет.
Все, что вам нужно помнить, это то, что базовые типы данных в коллекции доступны только для чтения. Объекты в коллекции могут быть изменены.
Вы можете прочитать больше об объектах в памяти здесь.
Добавление разных типов
Вы также можете добавлять различные типы предметов в коллекцию.
collFruit.Add "Яблоко" collFruit.Add 45 collFruit.Add #12/12/2019#
Это редко нужно. В VBA коллекции Sheets содержат листы типа Worksheet и типа Chart. (Чтобы создать лист с диаграммой, просто щелкните правой кнопкой мыши любую диаграмму, выберите «Переместить» и установите переключатель «Новый лист»).
Следующий код отображает тип и имя всех листов в текущей книге. Обратите внимание, что для доступа к другому типу необходимо, чтобы переменная For Each была вариантом, иначе вы получите ошибку.
Sub listi() Dim sh As Variant For Each sh In ThisWorkbook.Sheets ' Тип отображения и название листа Debug.Print TypeName(sh), sh.Name Next End Sub
При доступе к различным элементам переменная For Each должна быть вариантом. Если это не так, вы получите ошибку при доступе к другому типу, который вы объявили. Если мы объявим sh в качестве рабочего листа в приведенном выше примере, это приведет к ошибке при попытке доступа к листу типа Chart.
Редко вам понадобится коллекция разных типов, но, как видите, иногда это может быть полезно.
Добавление элементов с помощью ключа
Вы также можете добавить элементы, используя ключ, как показано в следующем примере:
collMark.Add Item:=45, Key:="Петр" Debug.Print "Оценки Петра: ",collMark("Петр")
Я включил имена параметров, чтобы сделать приведенный выше пример понятным. Однако вам не нужно этого делать. Просто помните, что ключ является вторым параметром и должен быть уникальной строкой.
Следующий код показывает второй пример использования ключей.
Sub IspolzovanieKlyuchei() Dim collMark As New Collection collMark.Add 45, "Петр" collMark.Add 67, "Юрий" collMark.Add 12, "Ольга" collMark.Add 89, "Елена" ' Печатать оценку Елены Debug.Print collMark("Елена") ' Печатать оценку Петра Debug.Print collMark("Петр") End Sub
Использование ключей имеет три преимущества:
- Если заказ изменится, ваш код все равно получит доступ к нужному товару
- Вы можете напрямую получить доступ к элементу, не читая всю коллекцию
- Это может сделать ваш код более читабельны
В коллекции Workbooks VBA доступ к рабочей книге гораздо лучше по ключу (имени), чем по индексу. Порядок зависит от порядка, в котором они были открыты, и поэтому является довольно случайным.
Sub IspolzovanieWorkbook() Debug.Print Workbooks("Пример.xlsm").Name Debug.Print Workbooks(1).Name End Sub
Когда использовать ключи
Пример использования ключей: представьте, что у вас есть набор идентификаторов для 10 000 учащихся вместе с их оценками.
У вас также есть несколько отчетов на листе, которые содержат списки идентификаторов студентов. Для каждого из этих рабочих листов вам необходимо распечатать отметку для каждого учащегося.
Вы можете сделать это, добавив 10 000 студентов в коллекцию, используя их идентификатор в качестве ключа. Когда вы читаете удостоверение личности с листа, вы можете получить прямой доступ к оценкам этого студента.
Если вы не используете ключ, вам придется искать по 10 000 идентификаторов для каждого идентификатора в отчете.
Недостаток использования ключей в коллекциях
В коллекциях есть два недостатка ключей
- Вы не можете проверить, существует ли Ключ.
- Вы не можете обновить значение, хранящееся в ключе
Первый вопрос легко обойти. Следующий код проверяет, существует ли ключ.
Function Exists(coll As Collection, key As String) As Boolean On Error Goto EH coll.Item key Exists = True EH: End Function
Вы можете использовать это так:
Sub TestExists() Dim coll As New Collection coll.Add Item:=5, key:="Яблоко" coll.Add Item:=8, key:="Слива" ' Печатает истина Debug.Print Exists(coll, "Яблоко") ' Печатает ложь Debug.Print Exists(coll, "Апельсин") ' Печатает истина Debug.Print Exists(coll, "Слива") End Sub
Второй вопрос не так легко обойти, если у вас нет хороших знаний в программировании.
Если вы хотите использовать ключи, есть альтернатива Коллекции. Вы можете использовать словарь. Словарь предоставляет больше возможностей для работы с клавишами. Вы можете проверить, существуют ли ключи, обновить значения в ключах, получить список ключей и так далее.
Доступ ко всем элементам в коллекции
Для доступа ко всем элементам в коллекции вы можете использовать цикл For или цикл For Each. Давайте рассмотри каждый из них.
Использование цикла For
В обычном цикле For вы используете индекс для доступа к каждому элементу. В следующем примере выводится имя всех открытых рабочих книг.
Sub VseWorkbook() Dim i As Long For i = 1 To Workbooks.Count Debug.Print Workbooks(i).Name Next i End Sub
Вы можете видеть, что мы используем диапазон от 1 до Workbooks.Count. Первый элемент всегда находится в первом положении, а последний элемент всегда находится в положении, указанном свойством Count коллекции.
В следующем примере распечатываются все элементы в пользовательской коллекции.
Sub IspolzovanieKollekcii() ' Объявить и создать коллекцию Dim collFruit As New Collection ' Добавить элементы collFruit.Add "Яблоко" collFruit.Add "Слива" collFruit.Add "Груша" ' Печать всех элементов Dim i As Long For i = 1 To collFruit.Count Debug.Print collFruit(i) Next i End Sub
Использование цикла For Each
Цикл For Each, который является специализированным циклом, используется для коллекций. Он не использует индекс, а формат показан в следующем примере.
Sub VseWorkbookForEach() Dim book As Variant For Each book In Workbooks Debug.Print book.Name Next End Sub
Формат цикла For:
For i = 1 To Coll.Count
Next
где i — long, Coll — коллекция
Формат цикла For Each:
For Each var In Coll
Next
где var — вариант, а Coll — коллекция
Чтобы получить доступ к каждому элементу:
For: Coll(i)
For Each: Var
В следующем примере показаны циклы для приведенного выше примера пользовательской коллекции.
Sub IspolzovanieOboihCiklov() ' Объявить и создать коллекцию Dim collFruit As New Collection ' Добавить элементы collFruit.Add "Яблоко" collFruit.Add "Слива" collFruit.Add "Груша" ' Печать всех элементов, используя For Dim i As Long For i = 1 To collFruit.Count Debug.Print collFruit(i) Next i ' Печать всех элементов, используя For Each Dim fruit As Variant For Each fruit In collFruit Debug.Print fruit Next fruit End Sub
For Each против For
Важно понимать разницу между двумя циклами.
Цикл For Each
- быстрее
- аккуратнее писать
- имеет только один заказ — от низкого индекса до высокого
Цикл For
- медленнее
- не так аккуратен
- можно получить доступ в другом порядке
Давайте сравним циклы по каждому из этих атрибутов
Скорость
For Each считается быстрее, чем цикл For. В настоящее время это проблема, только если у вас большая коллекция и / или медленный компьютер/сеть.
Аккуратнее
Цикл For Each аккуратнее писать, особенно если вы используете вложенные циклы. Сравните следующие циклы. Оба печатают названия всех рабочих листов в открытых рабочих книгах.
Sub PechatatNazvaniyaFor() ' Напечатайте названия рабочих листов из всех открытых рабочих книг Dim i As Long, j As Long For i = 1 To Workbooks.Count For j = 1 To Workbooks(i).Worksheets.Count Debug.Print Workbooks(i).Name, Workbooks(i).Worksheets(j).Name Next j Next i End Sub Sub PechatatNazvaniyaForEach() ' Напечатайте названия рабочих листов из всех открытых рабочих книг Dim bk As Workbook, sh As Worksheet For Each bk In Workbooks For Each sh In bk.Worksheets Debug.Print bk.Name, sh.Name Next sh Next bk End Sub
Цикл For Each гораздо удобнее для написания и менее подвержен ошибкам.
Порядок
Порядок цикла For Each всегда от самого низкого индекса до самого высокого. Если вы хотите получить другой заказ, вам нужно использовать цикл For. Порядок цикла For можно изменить. Вы можете прочитать предметы в обратном порядке. Вы можете прочитать раздел предметов или вы можете прочитать каждый второй элемент.
Sub ChitatSpravaNalevo() ' Просмотрите листы справа налево Dim i As Long For i = ThisWorkbook.Worksheets.Count To 1 Step -1 Debug.Print ThisWorkbook.Worksheets(i).Name Next i ' Пройдите первые 3 листа For i = 1 To 3 Debug.Print ThisWorkbook.Worksheets(i).Name Next i ' Пройдите каждый второй лист For i = 1 To ThisWorkbook.Worksheets.Count Step 2 Debug.Print ThisWorkbook.Worksheets(i).Name Next i End Sub
Цикл For дает больше гибкости, но реальность такова, что большую часть времени базовый порядок — это все, что вам нужно.
Сортировка коллекции
Для коллекции VBA нет встроенной сортировки. Однако мы можем использовать QuickSort.
Sub QuickSort(coll As Collection, first As Long, last As Long) Dim vCentreVal As Variant, vTemp As Variant Dim lTempLow As Long Dim lTempHi As Long lTempLow = first lTempHi = last vCentreVal = coll((first + last) 2) Do While lTempLow <= lTempHi Do While coll(lTempLow) < vCentreVal And lTempLow < last lTempLow = lTempLow + 1 Loop Do While vCentreVal < coll(lTempHi) And lTempHi > first lTempHi = lTempHi - 1 Loop If lTempLow <= lTempHi Then ' Поменять значения vTemp = coll(lTempLow) coll.Add coll(lTempHi), After:=lTempLow coll.Remove lTempLow coll.Add vTemp, Before:=lTempHi coll.Remove lTempHi + 1 ' Перейти к следующим позициям lTempLow = lTempLow + 1 lTempHi = lTempHi - 1 End If Loop If first < lTempHi Then QuickSort coll, first, lTempHi If lTempLow < last Then QuickSort coll, lTempLow, last End Sub
Вы можете использовать это так:
Sub TestSort() Dim coll As New Collection coll.Add "Москва" coll.Add "Тверь" coll.Add "Пенза" coll.Add "Новосибирск" QuickSort coll, 1, coll.Count Dim v As Variant For Each v In coll Debug.Print v Next End Sub
Использование коллекций с функциями и подпрограммами
Использовать коллекцию в качестве параметра или возвращаемого значения очень легко. Мы рассмотрим их по очереди.
Передача Коллекции в Sub / Function
Это просто передать коллекцию в функцию или подпункт. Она передается как любой параметр, как показано в следующем примере кода.
Sub IspolzovatColl() ' Создать коллекцию Dim coll As New Collection ' Добавить элементы coll.Add "Яблоко" coll.Add "Апельсин" ' Перейти на sub PrintColl coll End Sub ' Sub принимает коллекцию в качестве аргумента Sub PrintColl(ByRef coll As Collection) Dim item As Variant For Each item In coll Debug.Print item Next End Sub
Вы можете увидеть, насколько полезен вспомогательный PrintColl в примере. На нем будут напечатаны все элементы ЛЮБОЙ коллекции. Размер или тип элемента не имеет значения. Это показывает, насколько гибкими должны быть коллекции.
Передача ByVal против ByRef
Здесь следует помнить одну тонкую вещь: передача по значению (By Val) и передача по ссылке (ByRef) немного отличаются.
Для простой передачи переменной значение означает, что копия создана. Это означает, что если функция / подчиненный элемент изменится, значение не изменится, когда вы вернетесь к вызывающей процедуре.
В следующем примере мы передаем итоговое значение, используя ByVal и ByRef. Вы увидите, что после того, как мы передаем использование ByRef, значение изменилось в вызывающей процедуре.
Sub TipiPeredachi() Dim total As Long total = 100 PassByValue total ' Печатает 100 Debug.Print total PassByReference total ' Печатает 555 Debug.Print total End Sub Sub PassByValue(ByVal total As Long) ' значение изменилось только в этом sub total = 555 End Sub Sub PassByReference(ByRef total As Long) ' значение также изменилось за пределами этого sub total = 555 End Sub
Использование ByVal и ByRef с коллекцией немного отличается. Если вы добавляете или удаляете элемент, коллекция в исходном вызывающем абоненте также будет изменена. Таким образом, Subs в следующем примере удалят первый элемент из исходной коллекции.
Sub UdalitByRef(ByRef coll As Collection) coll.Remove 1 End Sub Sub RemoveByVal(ByVal coll As Collection) coll.Remove 1 End Sub
Причина этого заключается в том, что переменная Collection содержит указатель. Это означает, что он содержит адрес коллекции, а не фактическую коллекцию. Поэтому, когда вы добавляете или удаляете элемент, вы меняете то, на что указывает указатель, а не сам указатель. Однако, если вы измените указатель, он будет изменен за пределами подпрограммы.
Вам не нужно беспокоиться об указателях. Все, что вам нужно знать, это то, как это влияет на поведение передачи параметра. Если для параметра коллекции ничего не задано, поведение зависит от того, использовали ли вы ByRef или ByVal.
- Использование ByRef сбросит исходную коллекцию
- Использование ByVal не изменит оригинальную коллекцию
' Очистит исходную коллекцию Sub PassByRef(ByRef coll As Collection) Set coll = Nothing End Sub ' Не изменит исходную коллекцию Sub PassByVal(ByVal coll As Collection) Set coll = Nothing End Sub
Возврат коллекции из функции
Возврат коллекции из функции — это то же самое, что вернуть любой объект. Вам нужно использовать ключевое слово Set. В следующем примере вы увидите, как вернуть коллекцию.
Sub OtchetOFruktah() ' ПРИМЕЧАНИЕ. Мы не используем ключевое слово New для создания 'коллекции. ' Коллекция создается в функции CreateCollection. Dim coll As Collection ' получить coll от функции CreateCollection Set coll = CreateCollection ' сделать что-то с coll здесь End Sub Function CreateCollection() As Collection Dim coll As New Collection coll.Add "Слива" coll.Add "Груша" ' Возврат коллекции Set CreateCollection = coll End Function
Примечание: вы не используете ключевое слово New при объявлении коллекции в подпункте OtchetOFruktah(). Потому что коллекция создается в CreateCollection (). Когда вы возвращаете коллекцию, вы просто назначаете переменную коллекции, указывающую на эту коллекцию.
Заключение
Коллекции — очень полезная часть VBA. Их гораздо проще использовать, чем массивы, и они очень полезны, когда вы много добавляете и удаляете элементы. У них есть только четыре свойства:
Add, Remove, Count и Item . Поэтому они очень просты в освоении.
Основные пункты этой статьи:
- Коллекции — это способ хранения группы элементов вместе.
- VBA имеет свои собственные коллекции, такие как
Workbooks, Worksheets и Cells . - Элементы не обязательно должны быть одного типа, но обычно одного. Коллекция VBA Sheets может содержать как листы, так и листы диаграмм.
- Коллекция позволяет легко выполнять одну и ту же задачу для нескольких элементов, например, распечатать все значения.
- Коллекции похожи на массивы, поскольку в них хранятся группы похожих элементов.
- Коллекции лучше при добавлении и удалении большого количества элементов.
- Коллекции проще в использовании, чем массивы.
- Массивы более полезны, когда количество элементов фиксировано.
- Массивы более эффективны при чтении и записи в ячейки или из них.
- Базовые типы данных (т.е. не-объекты) в коллекции доступны только для чтения, а массивы — для чтения / записи.
- Вы можете создать коллекцию, используя только Dim или Dim с помощью Set.
- Вы можете удалить всю коллекцию, установив для нее значение Nothing. Но зависит от того, как она была создана.
- Вы можете добавлять элементы в определенную позицию в коллекции, используя аргументы «Before» и «After» с помощью функции Add.
- Вы можете использовать ключи с коллекцией для прямого доступа к элементу. Коллекции не имеют хорошей поддержки ключей, поэтому обычно лучше использовать коллекцию Dictionary, когда вам нужно использовать ключи.
- Вы можете использовать циклы For и For Each для доступа ко всем элементам в коллекции. Цикл For Each более эффективен, но позволяет просматривать коллекцию только в одном порядке.
- Вы можете легко передать коллекцию в качестве аргумента в функцию или подпрограмму.
- Вы можете легко вернуть коллекцию из функции.
Уровень сложности
Простой
Время на прочтение
5 мин
Количество просмотров 3.4K
Когда я впервые встретил этот класс, подумал «Зачем? Ведь есть простые массивы». А потом попробовал и не представляю как жил без него раньше.
Начну сразу с примера
Предположим, на активном листе в столбце 1 находится список ФИО сотрудников.
Наша задача собрать в массив только уникальные ФИО и отсортировать его по убыванию (ну такая вот немного странная задача). Сначала решим ее без использования ArrayList
, а в конце сравним результат.
Для получения уникальных значений, создаем функцию GetDistinctItems
и в нее передаем столбец с ФИО. В самой функции пробегаем циклом For Each
по всем ФИО и добавляем уникальные в объект Buffer
(Dictionary
). Далее методом Keys
извлекаем элементы в дополнительную функцию DescendingSort
(используем сортировку пузырьком) и получаем отсортированные значения в переменную Sorted
, которую и возвращаем как результат функции.
Public Sub Main()
Dim FullNameColumn As Range
Set FullNameColumn = ActiveSheet.UsedRange.Columns(1) ' Получаем первый столбец.
Dim DistinctList As Variant
DistinctList = GetDistinctItems(FullNameColumn) ' Передаем диапазон в функцию.
Debug.Print Join(DistinctList, vbCrLf) ' Выводим результат.
End Sub
Public Function GetDistinctItems(ByRef Range As Range) As Variant
Dim Data As Variant: Data = Range.Value ' Преобразуем диапазон в массив.
Dim Buffer As Object: Set Buffer = CreateObject("Scripting.Dictionary") ' Создаем объект Dictionary.
Dim Item
For Each Item In Data
If Not Buffer.Exists(Item) Then Buffer.Add Item, Empty ' Проверяем наличие элемента и добавляем если отсутствует.
Next
Dim Sorted As Variant
Sorted = DescendingSort(Buffer.Keys()) ' Сортируем функцией DescendingSort.
GetDistinctItems = Sorted ' Возвращаем результат.
End Function
Public Function DescendingSort(ByRef Data As Variant) As Variant
Dim i As Long
For i = LBound(Data) To UBound(Data) - 1
Dim j As Long
For j = i + 1 To UBound(Data)
If Data(i) < Data(j) Then
Dim Temp As Variant
Temp = Data(j)
Data(j) = Data(i)
Data(i) = Temp
End If
Next
Next
DescendingSort = Data
End Function
Тривиально? Вполне. Компактно? Ну в целом да, но в конце мы напишем еще более компактней, а заодно решим проблему написания новой функции, если вдруг результат нужно будет сортировать по возрастанию.
Что есть такое
Начнем с того, что ArrayList
это класс из пространства имен System.Collections
библиотеки mscorlib
, который реализует интерфейс IList
. Естественно, в VBA он несколько порезан в плане методов, иначе и быть не могло (например нет методов AddRange
или BinarySearch
). Но и тем не менее с ним можно (и нужно) работать.
По сути, это динамический массив. Его не нужно самостоятельно переопределять, чтобы изменить размерность, достаточно добавлять элементы с помощью метода Add
. Где-то я читал, что на низком уровне (да простят меня знатоки, я не знаю правильно ли я применяю это словосочетание здесь) есть свои нюансы в плане производительности, но, откровенно говоря, за все время использования этого объекта каких-либо проблем я не замечал и время работы макроса из-за него если и растет вообще, то совсем не критично.
В чем же сила брат удобство?
Как минимум в том, что это динамический массив. Вы просто добавляете элементы через метод и не нужно заморачиваться на тему ReDim
(и уж тем более Preserve
) и вычислений размеров будущего массива.
А дальше начинаются вкусняхи
Во-первых, мы можем выгрузить все элементы одним методом ToArray
. Как следует из названия, он преобразует все элементы объекта в обычный массив типа Variant
.
Во-вторых, мы можем составлять список уникальных значений, проверяя их наличие методом Contains
.
В-третьих, можно забыть про функцию UBound
, ведь у этого класса есть свойство Count
, которое, как не сложно догадаться, возвращает количество элементов помещенных в объект.
В-четвертых, есть возможность быстро отсортировать элементы как по возрастанию (метод Sort
), так и по убыванию (сначала используем метод Sort
, а после метод Reverse
).
Ну и быстро пробегаем по оставшимся свойствам:
Item(Index)
Предоставляет доступ к элементу по его индексу.
и методам:
IndexOf(Item, StartFrom)
Возвращает индекс элемента. Обязательный аргумент StartFrom
поможет найти каждый последующий индекс одинаковых элементов.
RemoveAt(Index)
Удаляет элемент по индексу.
Remove(Item)
Удаляет переданный элемент.
RemoveRange(StartPosition, Count)
Удаляет диапазон элементов. StartPosition
указывает на индекс первого элемента, Count
на количество элементов в удаляемом диапазоне.
Clear()
Удаляет все элементы.
Insert(Position, Item)
Добавляет элемент по заданной позиции.
Clone()
Создает копию объекта (по сути создает новый объект, а не возвращает ссылку на текущий).
Как создать это чудо
Создать объект класса ArrayList
можно с помощью функции CreateObject
:
Dim List As Object
Set List = CreateObject("System.Collections.ArrayList")
или через Tools -> Reference подключить библиотеку mscorlib.dll, а дальше создавать как обычный объект:
Dim List As New ArrayList
Минус и той и другой привязки в том, что интерфейс объекта вы не получите. Причину лично я не знаю, но почему-то VBA в Excel (больше нигде не проверял) не видит свойства и методы этого класса (в поздней привязке их и так нет ни у какого объекта, так как тип переменной Object
, а вот в ранней обычно есть).
Можно, конечно, получить часть интерфейса, объявив переменную с типом IList
и уже после этого присвоить ей инстанс ArrayList
, но тем самым мы потеряем бОльшую часть функционала, например методы Sort
, ToArray
, Reverse
.
Вернемся в начало
Помните наш пример? Предлагаю решение с новыми знаниями.
Теперь мы добавляем уникальные значения в объект Buffer
(ArrayList
), перед этим проверяя методом Contains
наличие ФИО в списке элементов. По окончанию цикла применяем метод Sort
и Reverse
для получения списка по убыванию. Выгружаем результат методом ToArray
. Согласитесь на этот раз все гораздо компактней.
Public Sub Main()
Dim FullNameColumn As Range
Set FullNameColumn = ActiveSheet.UsedRange.Columns(1) ' Получаем первый столбец.
Dim DistinctList As Variant
DistinctList = GetDistinctItems(FullNameColumn) ' Передаем диапазон в функцию.
Debug.Print Join(DistinctList, vbCrLf) ' Выводим результат.
End Sub
Public Function GetDistinctItems(ByRef Range As Range) As Variant
Dim Data As Variant: Data = Range.Value ' Преобразуем диапазон в массив.
Dim Buffer As Object: Set Buffer = CreateObject("System.Collections.ArrayList") ' Создаем объект ArrayList.
Dim Item
For Each Item In Data
If Not Buffer.Contains(Item) Then Buffer.Add Item ' Проверяем наличие элемента и добавляем если отсутствует.
Next
Buffer.Sort: Buffer.Reverse ' Сортируем по возрастанию, а потом переворачиваем (по убыванию).
GetDistinctItems = Buffer.ToArray() ' Выгружаем в виде массива.
End Function
Что в итоге
В итоге мы имеем преимущество перед классом Collection
в том, что есть проверка на наличие элемента в списке (без танцев с бубном) и быстрая выгрузка в виде массива (без написания цикла).
Перед классом Dictionary
, пожалуй, преимущество в отсутствии необходимости прописывать ключи (если они изначально не нужны).
Ну и оба вышеперечисленных проигрывают в плане сортировки, добавления элементов по индексу и т.д.
В общем и целом, достаточно удобный в применении класс для работы с одномерными массивами. Конечно, получать данные из объекта Range
гораздо проще в обычный массив, но если нужно создавать новый (например в цикле), то, как по мне, ArrayList
превосходный вариант.
P.S. (проблемки, проблемушки)
Уже после написания статьи обратил внимание, что мой пример на чистом ПК не работает, появляется automation error -2146232576 при создании объекта ArrayList.
Судя по этому ответу, для работы mscorlib необходимо включить .NET Framework 3.5.
Сделать это можно через Панель управления -> Программы -> Включение или отключение компонентов Windows -> поставить галочку напротив .NET Framework 3.5 (включает .NET 2.0 и 3.0) после чего на ПК скачаются необходимые файлы для работы компонента.
Обязательно после проделанных действий перезагрузить Excel. У меня при установке выдал ошибку. Исправилось выключением Excel и повторным включением компонента.
К слову на моем рабочем ПК таких проблем не было, т.е. данный компонент уже был подключен организацией (или по умолчанию в ранних Windows, не знаю точно).
Спасибо, что прочитали до конца.
Как насчет применения этого класса? Пишите в комментариях!
А также, подписывайтесь на мой телеграмм.