Visual Basic

Arrays

Arrays are now implemented using the OLE data type named SAFEARRAY. This is a data type that, like Variants and classes, allows arrays to be self-describing. The LBound and number of elements for each dimension of the array are stored in this structure. Within the inner workings of OLE, all access to these arrays is through an extensive set of API calls implemented in the system library file OLEAUT32.DLL. You do not get or set the array elements directly, but you use API calls. These API calls use the LBound and number of elements to make sure they always write within the allocated area. This is why they are safe arrays-attempts to write to elements outside the allowed area are trapped within the API and gracefully dealt with.

The ability to store arrays in Variants was new to Visual Basic 4, and a number of new language elements were introduced to support them such as Array and IsArray.

To set up a Variant to be an array, you can either assign it to an already existing array or use the Array function. The first of these methods creates a Variant whose subtype is the array value (8192) added to the value of the type of the original array. The Array function, on the other hand, always creates an array of Variants-VarType 8204 (which is 8192 plus 12).

The following code shows three ways of creating a Variant array of the numbers 0, 1, 2, 3:

Dim v As Variant
  Dim a() As Integer
  Dim i As Integer
  ' Different ways to create Variant arrays
  ' 1. Use the Array function
  v = Array(0, 1, 2, 3) 'of little practical use
  v = Empty
  ' 2. Create a normal array, and assign it to a Variant.
  ' Iterate adding elements using a normal array...
  For i = 0 To 3
      ReDim Preserve a(i) As Integer
      a(i) =  i
  Next i
  ' ...and copy array to a Variant
  v = a
  'or
  v = a()
  ' but not v() = a()
  v = Empty
  ' 3. Start off with Array, and then ReDim to preferred size
  ' avoiding use of intermediate array.
  For i = 0 To 3
      ' First time we need to create array
      If IsEmpty(v) Then
          v = Array(i)
      Else
          ' From then on, ReDim Preserve will work on v
          ReDim Preserve v(i)
      End If
      v(i) = i
  Next i

Notice that the only difference between the last two arrays is that one is a Variant holding an array of integers and the other is a Variant holding an array of Variants. It can be easy to get confused here, look at the following:

ReDim a(5) As Variant

This code is creating an array of Variants, but this is not a Variant array. What consequence does this have? Not much anymore. Before version 6 you could utilize array copying only with Variant arrays, but now you can do this with any variable-sized array.

So what is useful about placing an array in a Variant? As Variants can contain arrays, and they can be arrays of Variants, those contained Variants can themselves be arrays, maybe of Variants, which can also be arrays, and so on and so forth.

Just how deep can these arrays be nested? I don't know if there is a theoretical limit, but in practice I have tested at least 10 levels of nesting. This odd bit of code works fine:

Dim v As Variant, i As Integer
  ' Make v an array of two Variants, each of which is an array
  ' of two Variants, each of...and so on
  For i = 0 To 10
      v = Array(v, v)
  Next i
  ' Set a value...
  v(0)(0)(0)(0)(0)(0)(0)(0)(0)(0)(0) = 23

How do these compare to more standard multidimensional arrays? Well, on the positive side, they are much more flexible. The contained arrays-corresponding to the lower dimensions of a multidimensional array-do not have to have the same number of elements. Figure 4-2 explains the difference pictorially.

Figure 4-2 The difference between a standard two-dimensional array (top) and a Variant array (bottom)

These are sometimes known as ragged arrays. As you can see from the diagram, we do not have all the wasted space of a multidimensional array. However you have to contrast that with the fact that the Variant "trees" are harder to set up.

This ability of Variants to hold arrays of Variants permits some interesting new data structures in Visual Basic. One obvious example is a tree. In this piece of code, an entire directory structure is folded up and inserted in a single Variant:

Private Sub Form_Load()
      Dim v As Variant
      v = GetFiles("C:\") ' Places contents of C: into v
  End Sub
  Public Function GetFiles(ByVal vPath As Variant) As Variant
      ' NB cannot use recursion immediately as Dir
      ' does not support it, so get array of files first
      Dim vDir As Variant, vSubDir As Variant, i
      vDir = GetDir(vPath)
      ' Now loop through array, adding subdirectory information.
      If Not IsEmpty(vDir) Then
          For i = LBound(vDir) To UBound(vDir)
              ' If this is a dir, then...
              If (GetAttr(vDir(i)) And vbDirectory) = vbDirectory Then
                  ' replace dir name with the dir contents.
                  vDir(i) = GetFiles(vDir(i))
              End If
          Next i
      End If
      GetFiles = vDir
  End Function
  Private Function GetDir(ByVal vPath As Variant) As Variant
      ' This function returns a Variant that is an array
      ' of file and directory names (not including "." or "..")
      ' for a given directory path.
      Dim vRet As Variant, fname As Variant
      ' Add \ if necessary.
      If Right$(vPath, 1) <> "\" Then vPath = vPath & "\"
      ' Call the Dir function in a loop.
      fname = Dir(vPath, vbNormal & vbDirectory)
      Do While fname <> ""
          If fname <> "." And fname <> ".." Then
              vRet = AddElement(vRet, vPath & fname)
          End If
          fname = Dir()
      Loop
      ' Return the array.
      GetDir = vRet
  End Function
  Public Function AddElement(ByVal vArray As Variant, _
      ByVal vElem As Variant) As Variant
      ' This function adds an element to a Variant array
      ' and returns an array with the element added to it.
      Dim vRet As Variant ' To be returned
      If IsEmpty(vArray) Then
          ' First time through, create an array of size 1.
          vRet = Array(vElem)
      Else
          vRet = vArray
          ' From then on, ReDim Preserve will work.
          ReDim Preserve vRet(UBound(vArray) + 1)
          vRet(UBound(vRet)) = vElem
      End If
      AddElement = vRet
  End Function
Using + for String Concatenation

This misconceived experiment with operator overloading was considered bad form even back in the days of Visual Basic 2, when the string concatenation operator & was first introduced. Yet it's still supported in Visual Basic 6. In particular, since version 4 brought in extensive implicit type conversion between numerics and strings, this issue has become even more important. It's easy to find examples of how you can get tripped up. Can you honestly be confident of what the following will print?

Debug.Print "56" + 48
  Debug.Print "56" + "48"
  Debug.Print "56" - "48"

What should happen is that adding two strings has the same effect as subtracting, multiplying, or dividing two strings-that is, the addition operator should treat the strings as numeric if it can; otherwise, it should generate a type mismatch error. Unfortunately, this is not the case. The only argument for why the operator stays in there, causing bugs, is backward compatibility.

One point to note about this code is that this is an extremely efficient way of storing a tree structure, because as v is a multidimensional ragged array, the structure contains less wasted space than its equivalent multidimensional fixed-sized array. This contrasts with the accusation usually leveled at Variants, that they waste a lot of memory space.