Blog-Eintrag von Marco Cantù
Die kommende Version 10.4 von RAD Studio enthält eine interessante neue Funktion für die Sprache Delphi, nämlich die Möglichkeit, Datensätze mit benutzerdefiniertem Initialisierungs-, Finalisierungs- und Zuweisungscode zu erstellen.
Was ist ein verwalteter Datensatz in Delphi?
Datensätze in Delphi können Felder eines beliebigen Datentyps enthalten. Wenn ein Datensatz einfache (nicht verwaltete) Felder hat, wie numerische oder andere Aufzählungswerte, gibt es für den Compiler nicht viel zu tun. Das Erstellen und Entsorgen des Datensatzes besteht darin, Speicher zuzuweisen oder den Speicherplatz zu löschen. (Beachten Sie, dass Delphi standardmäßig keine Null-Initialisierung von Datensätzen vornimmt).
Wenn ein Datensatz ein Feld eines Typs enthält, der vom Compiler verwaltet wird (wie ein String oder eine Schnittstelle), muss der Compiler zusätzlichen Code einfügen, um die Initialisierung oder Finalisierung zu verwalten. Eine Zeichenkette wird beispielsweise als Referenz gezählt. Wenn der Datensatz den Gültigkeitsbereich verlässt, muss die Referenzzählung für die Zeichenkette innerhalb des Datensatzes verringert werden, was dazu führen kann, dass der Speicher für die Zeichenkette freigegeben wird. Wenn Sie einen solchen verwalteten Datensatz in einem Abschnitt des Codes verwenden, fügt der Compiler daher automatisch einen try-finally-Block um diesen Code herum ein und sorgt dafür, dass die Daten auch im Falle einer Ausnahme gelöscht werden. Dies ist schon seit langem der Fall. Mit anderen Worten: Verwaltete Datensätze sind Teil der Delphi-Sprache.
Datensätze mit Initialisierungs- und Finalisierungsoperatoren
In 10.4 unterstützt der Delphi-Datensatztyp nun eine benutzerdefinierte Initialisierung und Finalisierung, die über die Standardoperationen hinausgeht, die der Compiler für verwaltete Datensätze durchführt. Sie können einen Datensatz mit benutzerdefiniertem Initialisierungs- und Finalisierungscode deklarieren, unabhängig vom Datentyp seiner Felder, und Sie können einen solchen benutzerdefinierten Initialisierungs- und Finalisierungscode schreiben. Dazu fügen Sie dem Datensatztyp spezielle neue Operatoren hinzu (Sie können den einen ohne den anderen haben, wenn Sie möchten).
Nachfolgend finden Sie ein einfaches Codeschnipsel:
Typ
TMyRecord = Datensatz
Wert: Integer;
class operator Initialize (out Dest: TMyRecord);
class operator Finalize(var Dest: TMyRecord);
Ende;
Sie müssen natürlich den Code für die beiden Klassenmethoden schreiben, z.B. deren Ausführung protokollieren oder den Datensatzwert initialisieren - hier protokollieren wir auch einen Verweis auf einen Speicherplatz, um zu sehen, welcher Datensatz jede einzelne Operation ausführt:
class operator TMyRecord.Initialize (out Dest: TMyRecord);
begin
Dest.Value := 10;
Log('erstellt' + IntToHex (Integer(Pointer(@Dest)))));
end;
class operator TMyRecord.Finalize(var Dest: TMyRecord);
begin
Log('zerstört' + IntToHex (Integer(Pointer(@Dest)))));
end;
Der große Unterschied zwischen diesem Konstruktionsmechanismus und dem, was vorher für Datensätze verfügbar war, ist der automatische Aufruf. Wenn Sie etwas wie den nachstehenden Code schreiben, können Sie sowohl den Initialisierer als auch den Finalisierer aufrufen und haben am Ende einen Try-Final-Block, der vom Compiler für Ihre verwaltete Datensatzinstanz erzeugt wird.
Prozedur LocalVarTest; var my1: TMyRecord; begin Log (my1.Value.ToString); end;
Mit diesem Code erhalten Sie ein Protokoll wie:
erstellt 0019F2A8 10 zerstört 0019F2A8
Ein weiteres Szenario ist die Verwendung von Inline-Variablen, wie in:
begin var t: TMyRecord; Log(t.Value.ToString);
wodurch Sie die gleiche Sequenz im Protokoll erhalten.
Der Operator Zuweisen
Die :=-Zuweisung kopiert einfach alle Daten der Datensatzfelder. Dies ist zwar eine vernünftige Standardeinstellung, aber wenn Sie benutzerdefinierte Datenfelder und eine benutzerdefinierte Initialisierung haben, möchten Sie dieses Verhalten vielleicht ändern. Aus diesem Grund können Sie für benutzerdefinierte verwaltete Datensätze auch einen Zuweisungsoperator definieren. Der neue Operator wird mit der :=-Syntax aufgerufen, aber als Assign definiert:
class operator Assign (var Dest: TMyRecord; const [ref] Src: TMyRecord);
Die Operator-Definition muss sehr genaue Regeln befolgen. Dazu gehört, dass der erste Parameter ein Referenzparameter ist und der zweite als const per Referenz übergeben wird. Wenn Sie dies nicht tun, gibt der Compiler Fehlermeldungen wie die folgende aus:
[dcc32 Fehler] E2617 Der erste Parameter des Assign-Operators muss ein var-Parameter des Containertyps sein [dcc32 Hinweis] H2618 Zweiter Parameter des Assign-Operators muss ein const[Ref]- oder var-Parameter des Containertyps sein
Es gibt einen Beispielfall, in dem der Operator Zuweisen aufgerufen wird:
var my1, my2: TMyRecord; begin my1.Value := 22; my2 := my1;
erzeugt dieses Protokoll (in dem wir dem Datensatz auch eine Sequenznummer hinzufügen):
erstellt 5 0019F2A0 erstellt 6 0019F298 5 kopiert nach 6 zerstört 6 0019F298 zerstört 50019F2A0
Beachten Sie, dass die Reihenfolge der Zerstörung umgekehrt ist als die Reihenfolge des Aufbaus.
Übergabe verwalteter Datensätze als Parameter
Verwaltete Datensätze können anders funktionieren als normale Datensätze, auch wenn sie als Parameter übergeben oder von einer Funktion zurückgegeben werden. Hier finden Sie einige Routinen, die die verschiedenen Szenarien zeigen:
procedure ParByValue (rec: TMyRecord); procedure ParByConstValue (const rec: TMyRecord); procedure ParByRef (var rec: TMyRecord); procedure ParByConstRef (const [ref] rec: TMyRecord); function ParReturned: TMyRecord;
Ohne jetzt jedes Protokoll einzeln durchzugehen, hier die Zusammenfassung der Informationen:
- ParByValue erstellt einen neuen Datensatz und ruft den Zuweisungsoperator (falls verfügbar) auf, um die Daten zu kopieren. Die temporäre Kopie wird beim Verlassen der Prozedur zerstört.
- ParByConstValue macht keine Kopie und überhaupt keinen Aufruf
- ParByRef macht keine Kopie, kein Aufruf
- ParByConstRef macht keine Kopie, kein Aufruf
- ParReturned erstellt einen neuen Datensatz (über Initialize) und ruft bei der Rückkehr den Operator Assign auf, wenn der Aufruf wie im Folgenden aussieht, und löscht den temporären Datensatz, sobald er wie in zugewiesen wurde: my1 := ParReturned;
Ausnahmen und verwaltete Datensätze
Wenn eine Ausnahme ausgelöst wird, werden Datensätze im Allgemeinen gelöscht, auch wenn kein expliziter Try- und Finally-Block vorhanden ist, im Gegensatz zu Objekten. Dies ist ein grundlegender Unterschied und der Schlüssel zum wirklichen Nutzen von verwalteten Datensätzen.
procedure ExceptionTest;
begin
var a: TMRE;
var b: TMRE;
raise Exception.Create('Fehlermeldung');
end;
Innerhalb dieser Prozedur gibt es zwei Konstruktoraufrufe und zwei Destruktoraufrufe. Auch dies ist ein grundlegender Unterschied und ein Hauptmerkmal von verwalteten Datensätzen. Siehe den späteren Abschnitt über einen einfachen Smart Pointer, der auf verwalteten Datensätzen basiert.
Arrays von verwalteten Datensätzen
Wenn Sie ein statisches Array mit verwalteten Datensätzen definieren, werden diese initialisiert, indem Sie den Operator Initialize bei der Punktdeklaration aufrufen:
var
a1: array [1..5] of TMyRecord; // Aufruf hier
begin
Log ('ArrOfRec');
Sie werden alle zerstört, wenn sie aus dem Geltungsbereich herauskommen. Wenn Sie ein dynamisches Array mit verwalteten Datensätzen definieren, wird der Initialisierungscode mit der Größe des Arrays (mit SetLength) aufgerufen:
var
a2: Array von TMyRecord;
begin
Log ('ArrOfDyn');
SetLength(a2, 5); // hier aufrufen
Fazit
Dies ist nur eine relativ kurze Einführung in eine großartige neue Funktion, die Embarcadero der Sprache Delphi für die kommende Version 10.4 hinzufügt. Verwaltete Datensätze funktionieren zum Beispiel für generische Datensätze und in vielen anderen Szenarien, die hier nicht behandelt werden. Und obwohl dies die wichtigste neue Funktion der Sprache ist, gibt es noch weitere, wie die neue einheitliche Speicherverwaltung für alle Plattformen. Bleiben Sie dran!
Wenn Sie ein Update-Abonnement haben, erhalten Sie unter anderem Zugang zu den Beta-Builds der kommenden Versionen. Es ist noch Zeit, an unserem Beta-Programm für 10.4 teilzunehmen!
Dies ist eine Vorschau auf eine kommende Version von RAD Studio. Es kann immer wieder zu Fehlern oder Änderungen in letzter Minute kommen. Nichts hier ist endgültig, bis die Version offiziell zur Verfügung gestellt wird.

