Save Resources with UDT Arrays in Place of a Collection
This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
by Mike D. Jones
Application: Visual Basic 5.0/6.0
Visual Basic collections (small c) have definitely been a boon to the programming language. These structures let object models maintain entire groups of objects or data for use throughout an application. Often you may need to build your own collection of objects for use in a VB project. Of course, VB's Collection object provides the easiest way to do so. However, as you may know, this object can hog a lot of memory. For one, a VB Collection contains nothing but variant data types, which, as you know, have one of the highest costs with regard to system resources. Second, the Collection object uses a linked list storage structure that also adds to its overhead.
In some cases, you may not have any choice but to bite the bullet and use the Collection object. However, in many other instances, you can create a collection from an array of user-defined type variables (UDT) instead of the costly VB Collection object. Since both UDTs and arrays are inherent VB structures, they consume far less resources than a full-blown object. In this article, we'll explain which circumstances might warrant using a UDT array, and how to create a collection with them.
What's in a UDT?
Technically, a UDT provides a wrapper for one or more related elements. Most often, these elements contain data of various types that describe the UDT. These elements can consist of all the standard data types you're used to, including objects, other UDTs and variants. In many ways, a UDT is like a very basic object with the elements representing the properties. For instance, suppose you have a UDT named udtCat. It might contain elements that describe the cat's characteristics, like so:
Private Type udtCat Color As String Name As String Lives As Integer End Type
With this UDT in place, you then instantiate several udtCat variables and fill in the appropriate elements with values. To use a UDT, first place the Type declaration at module level. Then, declare a variable of that type anywhere in your project, like so:
Dim udtFluffy As udtCat Dim udtWhiskers As udtCat
At this point, you can use the individual UDT variables to either set or retrieve element values. This code would output 0 and 4 to the debug window:
With udtFluffy .Color = "Calico" .Name = "Fluffy" .Lives = 9 Debug.Print "Lives Used: " & (9 - .Lives) End With With udtWhiskers .Color = "White" .Name = "Whiskers" .Lives = 5 Debug.Print "Lives Used: " & (9 - .Lives) End With
|Note: Technically, the use of a With block in conjunction with a UDT doesn't improve an application's performance. Because UDTs are a base data type, VB loads the entire variable into memory, instead of just a pointer to an address, as it does with objects. As a result, VB doesn't have to make a round trip in order to access UDT members. However, the With keyword does cut down on keystrokes during development.|
Collecting cats in a UDT array
Even with this simple example, though, you can see how given a large number of cats it would quickly become tedious work--not to mention very inelegant--individually hard-coding the values and Lives Used output for each cat variable. Instead, a better solution would be to place the cat variables into a collection and loop through the collection, generating the appropriate value for each item with a single code statement. Typically, at this point, you might resort to a Collection object; but as we mentioned, in this case a UDT array provides a better alternative. You create a UDT array just like you do any other array:
Private udtCats(10) As udtCat
or for dynamic arrays:
Private udtCats() As udtCat
Of course, once you've created the array, you still need a way to set each item's element values. Again, you want to avoid hard-coding each element value for each cat. To this end, you can create a custom procedure that adds new items to the array and sets the appropriate values. Listing A shows one way we came up with to do just that.
Listing A: Adding new cats to the array collection
Private Sub AddCat(CatName As String, Color As String, _ Lives As Integer) Dim Upper As Integer On Error Resume Next Upper = UBound(udtCats) If Err.Number Then Upper = 0 Else Upper = UBound(udtCats) + 1 End If On Error GoTo 0 ReDim Preserve udtCats(Upper) With udtCats(Upper) .Color = Color .Name = CatName .Lives = Lives End With End Sub
As you can see, the AddCat() subroutine accepts the three cat characteristics as its parameters. When you call this procedure, first it tests the UBound() function on the collection array. If the array doesn't contain any items yet, then this function throws an error (subscript out of range). When it does, the procedure knows that array doesn't contain any items and sets the new upper boundary value to 0. If the UBound() function doesn't generate an error, then the array already contains at least one item. The code then adds one to this number to create the new upper boundary. Next, the code redimensions the array to include the new item, and sets the values for each element in the UDT. The following code illustrates how to use this procedure:
AddCat "Fluffy", "Calico", 9 AddCat "Whiskers", "White", 5 AddCat "Garfield", "Orange", 3 AddCat "Felix", "Black", 8
Manipulating UDTs in an array collection
Eventually, you're going to want to manipulate items in a UDT array. Depending on the circumstances, you have two main techniques to do so. First, of course, you can simply loop through the entire array and perform some action on each item. For instance, to display the lives used by every cat in the sample collection, we could execute the following code, which would produce the results shown in Figure A:
Dim x As Integer For x = LBound(udtCats) To UBound(udtCats) Debug.Print "Lives Used for " & _ udtCats(x).Name & ": " & _ (9 - udtCats(x).Lives) Next x
Figure A: To generate the lives used by each cat, we looped through the entire array.
Chances are, however, that you'll also want to extract a single item from the array based on some value, like an ID. In our example, we only have the cat's name as a unique identifier. Assuming that the array doesn't contain duplicates, or that we only want the first UDT that matches the requested name, we could use the code in Listing B to extract a single UDT. As you can see, this function simply loops through the UDT array's elements and compares the Name element to the Name parameter. When it finds a match, it passes that UDT back to the calling procedure. Listing C shows how to use this function, and Figure B shows the results.
Listing B: Retrieving a single UDT from the collection
Private Function GetCat(Name As String) As udtCat Dim x As Integer For x = LBound(udtCats) To UBound(udtCats) With udtCats(x) If Not CBool(StrComp(Name, .Name, vbTextCompare)) Then GetCat = udtCats(x) Exit For End If End With Next x End Function
Listing C: Gathering information from a single UDT
Private Sub ShowCats() Dim myCat As udtCat myCat = GetCat("Felix") With myCat MsgBox "My cat's name is " & .Name & "." & vbNewLine _ & "He's a " & LCase(.Color) & " cat with " & vbNewLine _ & .Lives & " lives left." End With End Sub
Figure B: Our code extracts a single UDT from the array collection and displays the information in a message box.
Updating the POP3 sample application with UDT arrays
If you read this month's article "You've got mail--with Visual Basic and POP3," you may have noticed that there was a bit of redundant code in the clsPOPEngine class. To keep things simple, we created a method for each POP3 command that we wanted to use. For the most part, however, those methods performed similar actions. They each sent the requested command to the mail server, and then prepared a custom message for the GUI.
As an alternative, we can create a collection that holds each command, along with information about its custom message, in a UDT array. This collection will let us create a single class method that executes all POP3 commands.
Adding UDTs to the POP3 application
To begin, open the POP3 application once more, and open the Code window for clsPOPEngine. We'll need to add the Type declaration first, so in the class's general declarations section, insert the following code:
Private Type udtItem Index As Integer Key As String Text As String AppendArg As Boolean End Type Public mudtCommands() As udtItem
As you can see, the UDT contains Index, Key, Text and AppendArg elements. We'll use the Index element to identify each command, and the Key element to hold the command text. The Text member will contain the custom message to display in the GUI, and the AppendArg will indicate whether or not Visual Basic should append the command's argument to the end of the custom message in Text.
Now that we've created the UDT and the array, we'll need a way to add new items to the collection, as well as retrieve them from it. Listing D shows the code that accomplishes this task. In addition, we added the code to build the command collection. In many ways these procedures are similar to the ones we've already covered. To build the array, enter the following code statement in the class's Initialize() event:
Listing D: Adding and retrieving commands from the collection
Private Sub AddCommand(Index As Integer, Key As String, _ Optional Text As String = "", Optional AppendArg _ As Boolean = False) Dim Upper As Integer On Error Resume Next Upper = UBound(mudtCommands) If Err.Number Then Upper = 0 Else Upper = UBound(mudtCommands) + 1 End If On Error GoTo 0 ReDim Preserve mudtCommands(Upper) With mudtCommands(Upper) .Key = Key .Index = Index .Text = Text .AppendArg = AppendArg End With End Sub Private Function GetCommand(Index As Integer) _ As udtItem Dim x As Integer For x = LBound(mudtCommands) To UBound(mudtCommands) With mudtCommands(x) If Index = .Index Then GetCommand = mudtCommands(x) Exit For End If End With Next x End Function Private Sub BuildCommandList() AddCommand POP_CONNECT, "", "Connecting to: ", True AddCommand POP_USER, "USER", "User Name: ", True AddCommand POP_PASS, "PASS", "Password (*******)" AddCommand POP_STAT, "STAT", "Retrieving status" AddCommand POP_LIST, "LIST", "Retrieving list" AddCommand POP_RETRIEVE, "RETR", "Retrieving..." AddCommand POP_DELETE, "DELE", "Deleting..." AddCommand POP_QUIT, "QUIT", "Logging off server" End Sub
Consolidate the command methods
At this point, we're ready to consolidate all the redundant command methods in the class. To do so, first delete all the methods that relate to POP3 commands: Connect, User, Pass, Stat, List, RetrieveAll, DeleteAll and Quit. Next, in their place, enter the ExecuteCommand() procedure from Listing E.
Listing E: Executing commands with UDT information
Public Sub ExecuteCommand(CommandIndex As Integer, _ Optional Arguments As String = "") Dim cmd As udtItem cmd = GetCommand(CommandIndex) With cmd mintCurCmd = .Index mstrMessage = .Text If .AppendArg Then mstrMessage = mstrMessage & Arguments If .Index <> POP_CONNECT Then mwinConnection.SendData .Key & " " _ & Arguments & vbNewLine Else With mwinConnection .Close .Protocol = sckTCPProtocol .RemotePort = POP_DEFAULT_PORT .RemoteHost = Arguments .Connect End With End If End With End Sub
This procedure takes just two parameters: the index of the command you wish to execute, and any arguments necessary to issue that command. After receiving this information, the method retrieves the appropriate UDT based on the index number in CommandIndex. Next, it extracts information about the command from the UDT. For commands with indexes other than POP_CONNECT (0), the procedure simply sends the command along to the mail server. When you request a connection, however, the procedure performs the necessary actions on the class's internal Winsock object variable.
Altering the POP3 GUI code
As our last step, all we have left to do is call this new procedure from the GUI when the user clicks one of the command buttons. Listing F shows the code for this updated Click() event. If you remember, previously in this event each index value in the Select Case block executed its own method of the clsPOPEngine. Now all the code block does, for the most part, is provide any necessary arguments for the requested command.
Listing F: Executing the correct command
Private Sub cmdPOPCommand_Click(Index As Integer) Dim strArgs As String Select Case Index Case POP_CONNECT If Not CBool(Len(txtConnectTo)) Then MsgBox "Please Fill In The Server Name", _ vbOKOnly, "Connection Error" Exit Sub End If strArgs = txtConnectTo Case POP_USER strArgs = txtUserName Case POP_PASS strArgs = txtPassword Case POP_RETRIEVE strArgs = "1" Case POP_DELETE strArgs = "1" End Select clsPOP3.ExecuteCommand Index, strArgs DisplayMessage clsPOP3.Message End Sub
UDT array limitations
It's easy to see that while UDT arrays can be helpful in some circumstances, they wouldn't be appropriate in others. For instance, a UDT obviously can't contain its own methods. In order to provide much of the functionality that would be available in a self-contained class, we had to create procedures external to the UDT collection itself. Also, you may not be too keen on creating these custom procedures to add and retrieve elements from a UDT array, when the Collection object has this functionality and more already built in. Finally, Visual Basic limits how you can pass UDTs of a private scope to other external procedures.
In instances where you need certain robustness in your collection, a UDT array probably isn't the way to go. However, in cases like ours, where our collection needs were predictable and simple, a UDT array will definitely save time and resources. In this article, we've shown you how to create a collection based on these low footprint structures.
Copyright © 2001 Element K Content LLC. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Element K Content LLC is prohibited. Element K is a service mark of Element K LLC.