12

I want to know if exist a equivalent of (.net)

list<somefixedclass> 

in vb6

I know that exist collection in vb6 but it uses object (variant) instead of a specific object.

thanks.

magallanes
  • 6,583
  • 4
  • 54
  • 55

5 Answers5

24

There is no direct equivalent in VB 6 to the generic List<T> found in VB.NET. However, there is such a thing as a Collection in VB 6, which provides similar functionality. The primary difference is that a VB 6 Collection is not strongly-typed, which means that all objects are stored as Variants in the collection. In some cases, this can be beneficial, because it allows you to store many different types of data in the same collection, and in fact, VB uses this object internally. It's easy enough to use a Collection and up-cast objects as they are retrieved from the class, but there's little you can do. It's not possible to implement strongly-typed collections in the VB runtime.

That being said, there is a workaround you can implement. Similarly to how collections were implemented in early versions of VB.NET before generics were introduced, you can wrap the Collection in a class, where the only access to the internal Collection is through methods that you expose from this class. This design pattern is commonly referred to as a "custom collection".

This does have the benefit of automatically handling casting, and alleviates the consumers of your code from having to remember to mind implementation details like this. It takes care of the (all too likely) possibility that you'll be looping through a collection at run-time that is only supposed to contain one type of object, but accidentally had a second, incompatible type of object added that causes your code to throw an exception. Of course, the disadvantage is that you have to re-implement most of the functionality already provided by the Collection object yourself, in the form of public methods on your custom collection.

Here's an example of how you might go about that:

Public Class CustomerCollection

    ''#Internal collection, exposed by this class
    Private m_Customers As Collection

    Private Sub Class_Initialize()
        ''#Set up the internal collection
        Set m_Customers = New Collection
    End Sub

    Public Sub Add(ByVal cust as Customer, Optional ByVal key as String)
        ''#Add the Customer object to the internal collection
        If IsMissing(key) Then
            m_Customers.Add cust
        Else
            m_Customers.Add cust, key
        End If
    End Sub

    Public Property Get Count() As Integer
        ''#Return the number of objects in the internal collection
        Count = m_Customers.Count
    End Property

    Public Sub Remove(ByVal index As Variant)
        ''#Remove the specified object from the internal collection,
        ''# either by its index or its key
        m_Customers.Remove index
    End Sub

    Public Function Item(ByVal index As Variant) as Customer
        ''#Return the specified object from the internal collection,
        ''# either by its index or its key
        Set Item = m_Customers.Item(index)
    End Function

    Public Sub Clear()
        ''#Removes all objects from the internal collection
        Set m_Customers = New Collection
    End Sub

End Class

Note that in order to set the custom collection's Item property as the collection's default method (like the built-in Collection object), you need to follow these steps in the VB 6 IDE:

  1. From the "Tools" menu, click "Procedure Attributes"

  2. Select the name of your custom class from the "Name" combo box.

  3. When the dialog appears, click the "Advanced" button.

  4. Select the "(Default)" item in the "Procedure ID" combo box.

  5. Click "OK"


If you'd also like to allow enumeration of your custom class using the For Each syntax (also like the built-in Collection object), you can add a NewEnum function to your custom class:

Public Property Get NewEnum() As IUnknown
    ''#Provides support for enumeration using For Each
    Set NewEnum = m_Customers.[_NewEnum]
End Property

Once you've done that, you need to instruct VB to use this property:

  1. As before, open the "Procedure Attributes" dialog from the "Tools" menu

  2. Select the name of your custom class from the "Name" combo box.

  3. When the dialog appears, click the "Advanced" button.

  4. Type the number "-4" in the "Procedure ID" combo box.

  5. Click "OK"

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • +1. IMHO this (correct) solution is helpful in a lot of cases, but for some other cases it may be a little bit too oversized. – Doc Brown Nov 14 '10 at 14:32
  • @Doc Brown: Agreed. Excellent choice of words. – Cody Gray - on strike Nov 14 '10 at 14:35
  • 2
    +1 But don't create the boilerplate yourself. Francesco Balena wrote [CollectionClassMaster](http://www.angryhacker.com/download/colclassmaster.zip), a free plug-in for the VB6 IDE that will do it for you automatically. This is now kindly hosted by [AngryHacker](http://angryhacker.com/blog/archive/2008/05/01/vb6-swiss-army-knife.aspx)) – MarkJ Nov 14 '10 at 20:00
  • 1
    CollectionClassMaster is now hosted on [devX](http://www.devx.com/vb2themax/CodeDownload/19713) – Our Man in Bananas Jun 02 '17 at 10:17
7

Here is our implementation of ArrayList. You can use it as a base (not through inheritance obviously but through composition as expressed in CodyGray's answer) for a strongly typed class, but if you don't need type safety it is much better than the Collection class.

Option Explicit

Private mavInternalArray() As Variant
Private mlArraySize As Long
Private mlCount As Long
Private mlGrowSize As Long
Private mfZeroIndex As Boolean

'---------------------------------------------------------------------------------------
' Procedure Clear
'---------------------------------------------------------------------------------------
Public Sub Clear()
          Dim index As Long

        For index = 0 To mlCount - 1
            If IsObject(mavInternalArray(index)) Then
                Set mavInternalArray(index) = Nothing
            End If
        Next index
        mlCount = 0

End Sub



'---------------------------------------------------------------------------------------
' Procedure Swap
'---------------------------------------------------------------------------------------
Public Sub Swap(Index1 As Long, index2 As Long)
          Dim vTmp As Variant


        If IsObject(mavInternalArray(index2)) Then
            Set vTmp = mavInternalArray(index2)
        Else
            vTmp = mavInternalArray(index2)
        End If

        If IsObject(mavInternalArray(Index1)) Then
            Set mavInternalArray(index2) = mavInternalArray(Index1)
        Else
           mavInternalArray(index2) = mavInternalArray(Index1)
       End If

       If IsObject(vTmp) Then
           Set mavInternalArray(Index1) = vTmp
       Else
           mavInternalArray(Index1) = vTmp
       End If


End Sub

Public Property Get ZeroIndex() As Boolean
        ZeroIndex = mfZeroIndex
End Property

Public Property Let ZeroIndex(fZeroIndex As Boolean)
        mfZeroIndex = fZeroIndex
End Property

Public Property Get GrowSize() As Long
        GrowSize = mlGrowSize
End Property

Public Property Let GrowSize(lNewSize As Long)
        Debug.Assert lNewSize > 0
        mlGrowSize = lNewSize
End Property

Private Sub Class_Initialize()
        mlGrowSize = 50
        mlArraySize = mlGrowSize
        mfZeroIndex = True
        mlCount = 0


        ReDim mavInternalArray(0 To mlGrowSize - 1)

End Sub

'---------------------------------------------------------------------------------------
' Procedure Remove
'---------------------------------------------------------------------------------------
Public Sub Remove(index As Long)
        Dim index2 As Long


        For index2 = index To mlCount - 2
            If IsObject(mavInternalArray(index2 + 1)) Then
                Set mavInternalArray(index2) = mavInternalArray(index2 + 1)
            Else
                mavInternalArray(index2) = mavInternalArray(index2 + 1)
            End If
        Next index2
          If mlCount <= 0 Then
            Exit Sub
          End If
        mlCount = mlCount - 1
        If IsObject(mavInternalArray(mlCount)) Then
           Set mavInternalArray(mlCount) = Nothing
       Else
           mavInternalArray(mlCount) = False
       End If
End Sub

'---------------------------------------------------------------------------------------
' Procedure Items
'---------------------------------------------------------------------------------------
Public Function Items(index As Long) As Variant
        If Not mfZeroIndex Then
            index = index - 1
        End If

        If index < mlCount And index >= 0 Then
            If IsObject(mavInternalArray(index)) Then
                Set Items = mavInternalArray(index)
            Else
                Items = mavInternalArray(index)
            End If
       End If
End Function

Public Sub SetItem(index As Long, Item As Variant)
        If Not mfZeroIndex Then
            index = index - 1
        End If
        If IsObject(Item) Then
            Set mavInternalArray(index) = Item
        Else
            mavInternalArray(index) = Item
        End If
End Sub

'---------------------------------------------------------------------------------------
' Procedure Add
'---------------------------------------------------------------------------------------
Public Function Add(vItem As Variant) As Long

        mlCount = mlCount + 1
        If mlCount > mlArraySize Then
            mlArraySize = mlArraySize + mlGrowSize
            ReDim Preserve mavInternalArray(0 To mlArraySize - 1)
        End If

        If IsObject(vItem) Then
            Set mavInternalArray(mlCount - 1) = vItem
        Else
            mavInternalArray(mlCount - 1) = vItem
       End If

       Add = mlCount - 1

End Function

'---------------------------------------------------------------------------------------
' Procedure ItemArray
'---------------------------------------------------------------------------------------
Public Function ItemArray() As Variant
        Dim vReturnArray As Variant

        vReturnArray = mavInternalArray
        ReDim Preserve vReturnArray(0 To mlCount - 1)
        ItemArray = vReturnArray
End Function

Public Function Count() As Long
        Count = mlCount
End Function


'---------------------------------------------------------------------------------------
' Procedure Insert
'---------------------------------------------------------------------------------------
Public Function Insert(index As Long, vItem As Variant) As Long
        Dim index2 As Long

        'Make sure array is large enough for a new item
        mlCount = mlCount + 1
        If mlCount > mlArraySize Then
            mlArraySize = mlArraySize + mlGrowSize
            ReDim Preserve mavInternalArray(0 To mlArraySize - 1)
        End If

        'Bump all the items with a higher index up one spot

        If index >= mlCount - 1 Then
            If IsObject(vItem) Then
                Set mavInternalArray(mlCount - 1) = vItem
            Else
               mavInternalArray(mlCount - 1) = vItem
           End If
       Else

           For index2 = mlCount - 1 To index + 1 Step -1
               If IsObject(vItem) Then
                   Set mavInternalArray(index2) = mavInternalArray(index2 - 1)
               Else
                   mavInternalArray(index2) = mavInternalArray(index2 - 1)
               End If
           Next index2

           If IsObject(vItem) Then
               Set mavInternalArray(index) = vItem
           Else
               mavInternalArray(index) = vItem
           End If
       End If
       Insert = mlCount - 1

End Function


Public Sub Clone(ByRef cDestinationDynamicArray As clsDynamicArray)
        Dim index As Long

        If cDestinationDynamicArray Is Nothing Then
            Set cDestinationDynamicArray = New clsDynamicArray
        End If

        cDestinationDynamicArray.Clear

        For index = 0 To mlCount - 1
            Call cDestinationDynamicArray.Add(mavInternalArray(index))
        Next index

End Sub

Public Property Get NewEnum() As IUnknown
    ''#Provides support for enumeration using For Each
    Set NewEnum = m_Customers.[_NewEnum]
End Property
Kris Erickson
  • 33,454
  • 26
  • 120
  • 175
  • +1 Interesting solution. Just out of curiosity: Have you benchmarked this as being faster for additions/insertions than the `Collection` class? Or in what way did you mean that it was better? – Cody Gray - on strike Nov 16 '10 at 06:04
  • In what way is it better? Collection can accidentally turn into a dictionary, without your knowing. Then your count is, say 5, but the items are (0,1,4,5,6) you get an error when you try to access element 3 and get an error. Also, it is faster IF you set GrowSize to be appropriate for the size of your array (yeah it could probably improve itself by doubling growsize each time it grows but thats a micro-optimization that isn't worth the extra code). However these days (when it was written the speed of a CPU was around 200Mhz) efficiency isn't what is important, not having a bug is. – Kris Erickson Nov 17 '10 at 04:31
2

VB6 is an ancient language. It doesn't contain template-like types as there are in modern languages (C++, C#, Java). So you will have to store your objects as Variants in the collection and then cast them back to your object type later.

ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
  • I usually work with c# in various project but for stand alone executables. Why?. Because .net is slow (in most cases), it can be decompiled easily, it required the net framework, it does not work with most OS, vb6 can runs from windows95 to linux (with wine without much effort).And java for stand alone executable is "funky" at best. My other option is Delphi and c++ but it is another story. – magallanes Nov 14 '10 at 13:59
  • 7
    Your reasoning is wrong. VB6 lacks generic types not because it's an ancient language (which it isn't, C++ is much older), but because it was a conscious decision to keep this kind of complexities away of the language that has a completely different niche. – GSerg Nov 14 '10 at 14:04
  • 1
    Note that I didn't downvote, because -2 is more than low enough for a post that does indeed provide the correct answer. But I think it's worth pointing out that we as programmers don't *always* have the luxury of choosing whatever language we want. Your suggestion isn't really helpful, and probably more frustrating than anything. And for what it's worth, you might remember life before generics in that "ancient" C# 1.1... – Cody Gray - on strike Nov 14 '10 at 14:33
  • That comment was mainly meant to express my strong dislike of the BASIC language/syntax itself. But I guess it'd have fit better in a comment than in an answer. – ThiefMaster Nov 14 '10 at 15:34
2

EDIT: if Cody Gray's solution is too oversized for your needs, you might try instead the "poor man's list" solution, as follows:

 Dim l() as somefixedclass
 Redim l(0)

 '...
 'increase size dynamically:
 Redim Preserve l(ubound(l)+1)
 '...

Of course, a List<somefixedclass> (in C#) or a List(Of somefixedclass) in VB.NET is much more "user-friendly" because it has methods like Find, Remove, AddRange and some other helpful things. The old VB6 construct deals very badly with the "empty list" case. Not to forget, List<..> increasement has much better performance for big lists (size>1000).

Doc Brown
  • 19,739
  • 7
  • 52
  • 88
  • 1
    This is a plausible solution, except that the performance of `Redim Preserve` can be killer if you find yourself calling it a lot. Using arrays or a `Collection` in VB 6 is an important design decision, and the best advice is that if you know (or can closely approximate) the number of item you'll have ahead of time, you should opt for an array. The performance is slightly better than a `Collection`, but if you'll be dynamically adding and removing objects, a `Collection` is probably the best choice. – Cody Gray - on strike Nov 14 '10 at 14:35
  • @Cody Gray: yes indeed, performance can get bad this way. In the most real-world programs I wrote the last 5 years in Excel-VBA, the 98% case of a list is a list with less than 500 entries, where this solution if completely sufficient. – Doc Brown Nov 14 '10 at 14:42
0

Dictionary is the best way to contain any object.

Don
  • 23
  • 1
  • 4