vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#
Mails senden, abrufen und decodieren - ganz easy ;-)  
 vb@rchiv Quick-Search: Suche startenErweiterte Suche starten   Impressum  | Datenschutz  | vb@rchiv CD Vol.6  | Shop Copyright ©2000-2024
 
zurück
Rubrik:    |   VB-Versionen: VB601.06.05
Die Erstellung persistenter Klassen

Speichern des Inhalts von Klasseninstanzen für spätere Wiederverwendung

Autor:  Manfred BohnBewertung:     [ Jetzt bewerten ]Views:  1.795 
ohne HomepageSystem:  Win9x, WinNT, Win2k, WinXP, Win7, Win8, Win10, Win11 Beispielprojekt 

Was bedeutet eigentlich persistent?
fortbestehend, beständig, anhaltend, dauernd, beharrlich

'Persistente Klassen' sind Klassenmodule, die Instanzen erstellen, deren enthaltene Daten auch nach dem Zerstören einer Instanz erhalten bleiben.

Bei manchen Klassen ist es sinnvoll, die aktuelle Einstellung der Eigenschaften, die enthaltenen Daten, benötigte Tabellen (Hilfswerte) oder 'innere Zustände' (z.B. Ergebnisse von Berechnungen oder vorgenommene Sortierungen) einer Klassen-Instanz für die spätere Wiederverwendung zu speichern.

Schlüssel in der Windows-Registry oder die Init-, Read- Write-Properties-Prozeduren sog. 'persistenter' Klassen in ActiveX-Komponenten sind nur für die Speicherung einer geringen Anzahl von Informationen geeignet. Große Datenmengen müssen in einer Datei abgelegt werden.

Es ist in solchen Fällen zweckmäßig, alle Variablen, die die zu speichernden Daten enthalten, in einem benutzerdefinierten Datentyp (UDT) zusammenzufassen. UDT-Variable können durch einen einzigen PUT#-Befehl in eine Datei geschrieben und per GET# auch wieder gelesen werden. Der Zugriff auf die Eigenschaften und sonstigen Hilfsvariablen der Klasse erfolgt dann über die Elemente einer UDT-Variable, die im Deklarationsteil der Klasse definiert wird.

Die Leistungsfähigkeit des binären Dateizugriffs durch PUT/GET wird oft unterschätzt.
PUT schreibt UDT-Variablen, die Strings variabler Länge, VARIANT-Variablen, andere UDT-Variablen oder sogar dynamisch deklarierte mehrdimensionale Arrays enthalten. Dabei werden automatisch Zusatzinformationen erstellt und in die Datei geschrieben, die das Einlesen mit GET ermöglichen:

  • die Länge von Strings
  • der Datentyp von Variant-Variablen
  • die Angaben zur Dimensionierung von Arrays

Das Besondere dabei ist die hohe Geschwindigkeit dieser Lese- und Schreibvorgänge. Große Datenmengen können in Sekundenbruchteilen gelesen und geschrieben werden. Der Ablauf vollzieht sich um ein vielfaches schneller als bei umfangreicheren Registry-Zugriffen, sequentiellen Datei-Streams oder Datenbank-Transaktionen.

Aus diesem Grund ist es möglich, im TERMINATE-Ereignis einer Klasse eine Datei mit Daten zu füllen (PUT) und im INITIALIZE-Ereignis der Klasse diese Datei wieder zu lesen (GET). Das Programm wird dadurch unter normalen Umständen nicht 'ausgebremst'. Es bietet sich somit eine komfortable Möglichkeit auch umfangreichere Datenmengen, die in einer Klasseninstanz enthalten sind, 'automatisch' zu speichern und zu laden.

Einige Einschränkungen sind zu beachten:

  • Die UDT-Variable darf keine Objektverweise enthalten. Sie zu speichern wäre auch sinnlos.
  • Die UDT-Variable sollte keine VARIANT-Variablen enthalten, die auf ein Array oder ein Objekt verweisen.

Die Funktion GET erwartet beim binären Dateizugriff, dass die Daten in der Datei der UDT-Variable entsprechen, die eingelesen werden soll. Gibt es dabei Diskrepanzen, können Programmabstürze oder andere unvorhergesehene Ereignisse eintreten. Zumindest stimmt dann die Belegung der Elemente der UDT-Variable nicht.

Es muss deshalb sicher gestellt werden, dass der Inhalt der Datei mit der UDT-Variable korrespondiert, in die die Daten gelesen werden sollen.
Überwacht werden kann dies durch den Eintrag einer Versionskennung beim Schreiben mit PUT und Überprüfung durch GET. Dadurch ist aber noch nicht sicher gestellt, ob die Datei womöglich beschädigt oder sogar editiert worden ist. Die Versionskennung sollte deshalb von PUT am Schluss der Datei wiederholt und ein Zeitstempel eingetragen werden. Dieser Stempel kann mit dem Zeitpunkt der 'letzten Änderung' der Datei vor dem Lesen verglichen werden. Abweichungen deuten auf eine nachträgliche Manipulation der Datei hin.

Anwendungsbeispiel:
Die Klasse 'clsVariantArray' kapselt ein Array vom Datentyp VARIANT.
Zusätzlich ist das 'Rückgängig-Machen' von Wertzuweisungen implementiert.

Die aktuellen Daten der Klasse werden (falls das Array dimensioniert ist) im Terminate-Ereignis durch PUT gespeichert und im INITIALIZE-Ereignis durch GET gelesen.
Durch Auslagerung der Dateizugriffe in die Methoden 'LoadMembers' und 'SaveMembers' kann man an jeder beliebigen Stelle des Programms den aktuellen Klasseninhalt in einer (optional: vorzugebenden) Datei speichern bzw. wieder laden.
Auf diese Weise ist es auch möglich, jeder Instanz einer Klasse eine separate Datei zuzuordnen (durch Aufruf von 'Loadmembers' [mit einem Dateinamen im Parameter] nach Erstellung der Instanz).
Um zu verhindern, dass der Daten-Inhalt einer Instanz durch deren Terminate-Ereignis in die 'allgemeine' Klassendatei ('cMemberFile') eingetragen wird, kann man eine entsprechende Eigenschaft bei der Klasse ergänzen (im Beispiel: NICHT_SPEICHERN) und in der Klassen-Instanz auf 'true' setzen.

Die Funktion 'Variant_Array_Demo' demonstriert die Arbeitsweise der Klasse 'clsVariantArray'. Beim Ausführen erzeugt das Beispiel zwei Dateien im Anwendungspfad ('clsVariantArray.mbv', 'Inhalt_Von_VariantArray.mbv'). Bereits vorhandene gleichnamige Dateien werden ohne Nachfrage überschrieben!

Man beachte, dass die Funktionen PUT/GET das dynamisch deklarierte Variant-Array, dessen Feld-Elemente verschiedene Untertypen enthalten, auch als UDT-Komponente verarbeiten können.

Hinweise:
Falls eine Klassen-Zugriffsvariable bereits auf eine Instanz verweist und durch SET mit dem Schlüsselwort NEW eine neue Instanz der Klasse erzeugt wird, wird zuerst das INITIALIZE-Ereignis der neuen Instanz ausgeführt, ehe das TERMINATE-Ereignis der freigegebenen Instanz erfolgt. (Falls weitere Objekt-Referenzen bestehen, wird das TERMINATE-Ereignis überhaupt nicht ausgeführt.) Dies hat natürlich Auswirkungen darauf, ob bzw. welche Daten die neu erstellte Instanz enthält.

Falls in einer Klassendatei die klassenglobalen PRIVATE-Konstanten 'cVersion', 'cMemberFile' und das Ereignis 'FehlZugriff' ergänzt worden sind und die zu speichernden Daten als Elemente in der klassenglobalen PRIVATE-UDT-Variable 'gMembers' enthalten sind, können die Routinen 'SaveMembers' und 'LoadMembers' als Klassen-Methoden aus dem Anwendungsbeispiel übernommen werden.

Sub VariantArray_Demo()
 
  ' Anwendungsbeispiel für Klasse 'clsVariantArray'
 
  ' Zugriffsvariable für Instanzen der Klasse
  ' (lokale Deklaration: das Ereignis 'Fehlzugriff'
  ' wird nicht ausgelöst)
  Dim a As clsVariantArray
  Dim b As clsVariantArray
  Dim c As clsVariantArray
 
  Dim arr() As Variant        ' Array für Vergleich
  Dim i As Long, N As Long    ' Loop
  Dim wert As Variant         ' Zuweisungsvariable
  Dim File As String          ' Datenausgabe 'c'
 
  ' separates File für Ausgabe des Inhalts einer Instanz
  ' (wird überschrieben!)
  File = App.Path + "\Inhalt_Von_VariantArray.mbv"
 
  ' eine Instanz der Klasse 'clsVariantArray' anfordern
  Set a = New clsVariantArray
 
  ' ggf. bereits bestehende Klassenbez. Datendatei löschen
  a.ClearFile
 
  N = 10000                   ' Arraygröße festlegen
  ReDim arr(1 To N)           ' Array für Vergleich dimensionieren
  a.Dimensionieren 1, N       ' Klassen-Array dimensionieren (1->N)
 
  ' Einen Kommentar eintragen (nach dem Dimensionieren!)
  a.Kommentar = "Array für Testzwecke - erstellt: " + CStr(Now)
 
  ' Array und Vergleichsarray mit Zufallszahlen füllen
  For i = 1 To N
    ' Double-Zufallszahl (-10000 bis 10000) erstellen
    wert = CDbl(Rnd * 1000)
    If Rnd > 0.5 Then wert = -wert
 
    ' Verschiedene Variant-Untertypen erstellen
    If i Mod 5 = 1 Then
      wert = Int(wert)           ' Integer
    ElseIf i Mod 5 = 2 Then
      wert = CStr(wert)          ' String
    ElseIf i Mod 5 = 3 Then
      wert = CCur(wert)          ' Currency
    ElseIf i Mod 5 = 4 Then
      wert = CDec(i)             ' Dezimal
    End If
 
    arr(i) = wert                 ' Vergleichsarray füllen
    a.Zugriff(i) = arr(i)         ' Klassenarray füllen
 
    ' Inhalt von 'a' zwischendurch
    ' in einem separaten File speichern
    If i = N \ 2 Then
      a.SaveMembers File
    End If
  Next i
 
  ' 100 weitere Zuweisungen, die nicht in das
  ' Vergleichsarray eingetragen werden
  For i = 1 To 500 Step 5
    a.Zugriff(i) = CDbl(Rnd)
  Next i
 
  ' Instanz der Klasse zerstören
  ' Die Membervariablen werden gespeichert (TERMINATE)
  Set a = Nothing
 
  ' Neue Instanzen der Klasse anfordern:
  ' Die gespeicherten Membervariablen werden in alle
  ' Instanzen der Klasse geladen (INITIALIZE)
  Set a = New clsVariantArray
  Set b = New clsVariantArray
  Set c = New clsVariantArray
 
  ' Die 100 weiteren Zuweisungen wieder rückgängig machen:
  ' in den Objekt-Instanzen 'a' und 'b'!
  While a.Rückgängig_Machen_Möglich
    a.Rückgängig_Machen
    b.Rückgängig_Machen
  Wend
 
  ' Die Instanz 'c' soll nicht den Inhalt der
  ' Standard-Klassendatei enthalten, sondern
  ' das zwischengespeicherte Array in 'File'
  c.LoadMembers File
 
  ' c' enthält jetzt nur die Werte 1 bis N \ 2
  For i = c.Untergrenze To N
    If i <= N \ 2 Then
      If c.Zugriff(i) <> a.Zugriff(i) Then Stop
    Else
      ' leerer Bereich von 'c'
      If c.Zugriff(i) <> vbEmpty Then Stop
    End If
  Next i
 
  ' Prüfen des Inhalts der Klassen-Instanzen:
  ' Es muss wieder der Inhalt des Vergleichsarray
  ' hergestellt sein (sonst STOP)
  For i = 1 To N
    ' Identiät der Variant-Untertypen prüfen
    If VarType(arr(i)) <> VarType(a.Zugriff(i)) Then Stop
    ' Identität der Variant-Werte prüfen
    If arr(i) <> a.Zugriff(i) Then Stop
    If arr(i) <> b.Zugriff(i) Then Stop
  Next i
 
  a.Clear  ' Inhalt von Array 'a' Löschen
 
  ' Inhalt des Array in 'b' auf 'a' zuweisen (kopieren)
  a.ArrayZuweisen = b
 
  ' In 'a' muss jetzt wieder 'b' stehen
  For i = b.Untergrenze To b.Obergrenze
    If a.Zugriff(i) <> b.Zugriff(i) Then Stop
  Next i
 
  ' Der Inghalt der nur teilweise gefüllten
  ' Instanz 'c' soll nicht in der klassenbez.
  ' Daten-Datei gespeichert werden:
  c.Speichern_Verboten = True
 
  MsgBox "Variant-Array Demo abgeschlossen"
 
  ' Hier werden die lokal deklarierten Instanzen
  ' der Klasse automatisch zerstört,
  ' aber die Daten-Inhalte zuvor gespeichert (nur a,b)
 
  ' Wer auf 'END SUB' einen Breakpoint setzt,
  ' kann den Ablauf der Terminate-Ereignisse verfolgen
End Sub