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.

March 2001

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
    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.
[ Figure A ]

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)) 
        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.
[ Figure B ]

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
    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
    With mwinConnection
        .Protocol = sckTCPProtocol
        .RemotePort = POP_DEFAULT_PORT
        .RemoteHost = Arguments
    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
    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
    strArgs = "1"
    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.