Visual Basic - Technik, FAQ, Tricks, Beispiele

Home / Daten / Array / BigArray

Wirklich große Felder mit assoziativem Zugriff

Historie
27.02.2008Workaround für Microsoft-Bug in Collection (arbeitet Fehlerhaft, wenn Key ein vbNullChar enthält);
Vielen Dank an Franz Reiter aus Österreich für den Hinweis!
12.04.2002Hinweis auf GlobalMultiUse-Klasse für Konstruktor
17.04.2001Erste Version

Einführung

Im Rahmen dieses Artikels wird eine Klasse vorgestellt, die Array-ähnlichen Zugriff auf (theoretisch) beliebig große Datenmengen ermöglicht. Nach Festlegung des Ist- und Soll-Zustands (s.a. Beispiel) wird der realisierte Quelltext genaustens analysiert. Ein objektorientiertes Schmankerl ist die Realisierung eines Konstruktors für die vorgestellte Klasse.

Problem (Ist-Zustand)

Ein Feld in VB ist im Vergleich zu denen der meisten anderen Programmiersprachen äußerst flexibel. So ermöglicht ReDim Preserve dynamische Größenänderungen, ohne das die Inhalte verloren gehen. Einschränkungen gibt es aber in den folgenden Bereichen:

Lösung (Soll-Zustand)

Nachfolgend wird die BigArray-Klasse vorgestellt, welche die o.g. Einschränkungen nicht hat. Daher ist sie für die Darstellung von "dünn-besetzten Matrixen" besonders geeignet.

Die Klasse bietet folgende Methoden und Eigenschaften:

obj.Clear [Default]
löscht den gesamten Inhalt; optional kann ein Default-Wert angegeben werden. (Default-Werte werden nicht wirklich gespeichert!)
x = obj.Default
liefert den aktuellen Default-Wert zurück.
x = obj.Exists(Index1, Index2, ...)
zeigt an, ob ein Wert existiert.
x = obj.Item(Index1, Index2, ...)
gibt den Wert zurück; existiert der Wert nicht, so wird der Default-Wert zurückgegeben.
obj.Item(Index1, Index2, ...) = x
speichert den angegebenen Wert.
Set obj.Item(Index1, Index2, ...) = x
speichert das angegebene Objekt (genauer: den Objekt-Verweis).

Man beachte, dass alle Index-Angaben sowohl numerisch als auch alphanumerisch (deswegen "assoziativ") sein können. Item ist die Default-Eigenschaft. Daher kann verkürzend auch folgendes geschrieben werden:

x = obj(Index1, Index2, ...)
obj(Index1, Index2, ...) = x
Set obj(Index1, Index2, ...) = x

Der Zugriff kann also exakt wie bei einem Standard-Array erfolgen.

Tipp: Für Freunde der Objektorientierung wird unten auch gezeigt, wie man sogenannte Konstruktoren in VB elegant simulieren kann, so dass der Klassen-Anwender dies gar nicht bemerkt. Eine neues BigArray kann dadurch in einem Rutsch initialisiert werden:

Dim obj As BigArray
Set obj = BigArray([Default])

Beispiel

Im folgenden Beispiel wird ein BigArray ba mit Default-Wert "nix drin" angelegt. Es wird ein Element in eine bestimmte Position geschrieben, danach wird es wieder überschrieben. Anschließend wird das globale Screen-Objekt auch noch abgelegt, und dann werden alle Elemente aufgelistet. Nach dem Zurücksetzen des Arrays wird eine bestimmte Position abgefragt, und dann getestet, ob dort überhaupt ein Wert gespeichert ist.

Dim ba As BigArray
Dim v As Variant

'BigArray anlegen:
Set ba = BigArray("nix drin")

'Strings rein-/überschreiben:
ba(2, 3, 4, "Wacker", 5) = "Willi"
ba(2, 3, 4, "Wacker", 5) = "Wusel"

'Objekt reinschreiben:
Set ba(2) = Screen

'Alle Elemente auflisten:
For Each v In ba
  Print TypeName(v) 'ergibt "String" und "Screen"
Next v

'Array löschen:
ba.Clear "ganz leer"
Print ba(2)        'ergibt "ganz leer"
Print ba.Exists(2) 'ergibt "False"

Code / Quelltext

Allgemeines

Legen Sie ein neues Klassenmodul mit dem Namen "BigArray" an. Fügen Sie im Deklarationsteil den Klassen-/Objekt-weit reichenden Code ein:

'Status-Variablen:
Private pDefault As Variant  'Default-Wert
Private pItems As Collection 'Daten-Speicher

'Ein neues BigArray ist "jungfräulich":
Private Sub Class_Initialize()
  Clear
End Sub

Hilfs-Routinen

Folgende Routinen erleichtern die Realisierung der Methoden und Eigenschaften. Basis ist eine Collection, in der ein Element unter einem bestimmten Key-String (gebildet aus der Positions-Angabe) abgelegt wird, falls das Element nicht dem Default-Wert entspricht.

'Index-Liste in Key-String umwandeln:
Private Function IndexKey(ByRef Index As Variant) As String

  Dim i As Long

  For i = LBound(Index) To UBound(Index)
    IndexKey = Index(i) & vbTab & IndexKey 'vbNullChar' funzt nicht!?!
  Next i

End Function

'Entspricht Element dem Default-Wert?
Private Function IsDefault(ByRef Value As Variant) As Boolean

  If IsNull(pDefault) Then
    IsDefault = IsNull(Value)
  ElseIf IsEmpty(pDefault) Then
    IsDefault = IsEmpty(Value)
  ElseIf IsNull(Value) Or IsEmpty(Value) Then
    Exit Function
  ElseIf IsObject(pDefault) Then
    If IsObject(Value) Then IsDefault = (Value Is pDefault)
  ElseIf Not IsObject(Value) Then
    IsDefault = (Value = pDefault)
  End If

End Function

'Element unter Key speichern:
Private Sub ItemAdd(ByRef Key As String, ByRef Value As Variant)
  KeyRemove Key
  If Not IsDefault(Value) Then pItems.Add Value, Key
End Sub

'Existiert Element unter Key?
Private Function KeyExists(ByRef Key As String) As Boolean

  On Error Resume Next
    KeyExists = Len(TypeName(pItems.Item(Key)))
  On Error GoTo 0

End Function

'Element unter Key löschen:
Private Sub KeyRemove(ByRef Key As String)

  On Error Resume Next
    pItems.Remove Key
  On Error GoTo 0

End Sub

Methoden und Eigenschaften

Die nach außen sichtbaren Routinen (sprich die öffentlichen Methoden und Eigenschaften) brauchen nur noch die oben gezeigten Hilfs-Routinen zur richtigen Zeit aufrufen. Dabei muss u.U. unterschieden werden, ob es sich bei dem Element um einen einfachen Wert oder um ein Objekt handelt.

'BigArray zurücksetzen:
Public Sub Clear(Optional aDefault As Variant)

  Set pItems = New Collection
  If Not IsMissing(aDefault) Then

    If IsObject(aDefault) Then
      Set pDefault = aDefault
    Else
      pDefault = aDefault
    End If

  End If

End Sub

'Default-Wert auslesen:
Public Property Get Default() As Variant
  Default = pDefault
End Property

'Existiert Element an Index-Position?
Public Property Get Exists(ParamArray Index()) As Boolean

  Dim Key As String
  Key = IndexKey(CVar(Index))
  Exists = KeyExists(Key)

End Property

'Element an Index-Position auslesen:
'Procedure ID: (Default)
Public Property Get Item(ParamArray Index()) As Variant

  Dim Key As String

  Key = IndexKey(CVar(Index))
  If KeyExists(Key) Then

    'Wert auslesen:
    If IsObject(pItems.Item(Key)) Then
      Set Item = pItems.Item(Key)
    Else
      Item = pItems.Item(Key)
    End If

  Else

    'Default zurückgeben:
    If IsObject(pDefault) Then
      Set Item = pDefault
    Else
      Item = pDefault
    End If

  End If

End Property

'Element (einfacher Wert) speichern:
Public Property Let Item(ParamArray Index(), aItem As Variant)
  ItemAdd IndexKey(CVar(Index)), aItem
End Property

'Element (Objekt) speichern:
Public Property Set Item(ParamArray Index(), aItem As Variant)
  ItemAdd IndexKey(CVar(Index)), aItem
End Property

'For...Each für BigArray einrichten:
'Procedure ID: -4
Public Function NewEnum() As IUnknown
  Set NewEnum = pItems.[_NewEnum]
End Function

Vergessen Sie nicht, unter "Tools / Procedure Attributes" die angegebenen "Procedure IDs" einzustellen. Ansonsten funktioniert weder der komfortable Default-Zugriff auf die Item-Eigenschaft, noch die Enumeration durch alle im BigArray gespeicherten Elemente.

Ein Konstruktor für BigArray

Ein Konstruktor erlaubt die Generierung eines Objekts, indem man den Namen der Klasse wie eine Funktion aufruft. Dabei können alle eventuell für die Initialisierung notwendigen Informationen gleich als Parameter mit übergeben werden.

Legen Sie ein neues Standard-Modul mit dem Namen "BigArrayConst" an. Fügen Sie einfach folgenden Code ein:

Public Function BigArray( _
    Optional ByRef Default As Variant = Empty _
  ) As BigArray

  Set BigArray = New BigArray 'Neues Objekt generieren
  BigArray.Clear Default      'Objekt initialisieren

End Function

Wenn Sie zukünftig beide BigArray-Dateien in ihre Projekte einbinden, genügt der im Beispiel gezeigte Aufruf zur vollständigen Einrichtung eines BigArray-Objekts.

Möchten Sie aus dem Ganzen eine selbstständige ActiveX-Komponente erstellen, so nennen Sie die ActiveX-DLL etwa "BigArrayLib", und erstellen den Konstruktor (statt in einem Standard-Modul) in einer GlobalMultiUse-Klasse, damit er auch von überall her aufrufbar ist.

© Jost Schwider, 17.04.2001-27.02.2008 - http://vb-tec.de/bigarray.htm