Hier ist eine Frage, die zunächst unmöglich klingt: Kann ein Programm seinen eigenen Code untersuchen, während es läuft? Kann es ein Objekt betrachten, das es nie zuvor gesehen hat, fragen „Welche Properties hast du, wie heißen sie, welche Typen haben sie?" — und sie dann lesen und schreiben, ohne eine einzige Zeile, die speziell für die Klasse dieses Objekts geschrieben wurde?
Die Antwort lautet in modernem Delphi: ja. Und die Maschinerie, die das möglich macht, heißt RTTI — Run-Time Type Information. Sie ist der stille Motor hinter einem enormen Teil des Toolings, das Sie bereits verwenden: die Art, wie der Object Inspector der IDE die published Properties einer Komponente auflistet, wie FireDAC und ORMs Objektfelder auf Datenbankspalten abbilden, wie ein JSON-Serializer ein beliebiges Objekt in Text verwandelt und zurück. Keines dieser Werkzeuge wurde in Kenntnis Ihrer Klassen geschrieben — sie entdecken sie zur Laufzeit, über RTTI.
Die meisten Delphi-Entwickler profitieren seit Jahren von RTTI, ohne es je direkt aufzurufen. Das ist ein wenig schade, denn die moderne Unit System.Rtti — eingeführt bereits mit Delphi 2010 — macht Reflection wirklich zugänglich, und sobald der Groschen fällt, eröffnet sie eine ganze Kategorie eleganter, generischer Lösungen. Diese dreiteilige Serie ist eine behutsame, ehrliche Tour. Teil 1 beantwortet das „Was" und „Warum": was RTTI eigentlich ist, die zwei Records, mit denen alles beginnt (TRttiContext und TValue), und — wichtig — wann man nicht danach greifen sollte. Teil 2 liest Typen, Properties und Attribute; Teil 3 ruft Methoden auf und baut ein kleines, wirklich nützliches Werkzeug. Beginnen wir mit der Idee.
Reflection, in einem Satz
Das übergeordnete Konzept trägt einen Namen, den Sie in vielen Sprachen wiederfinden: Reflection — die Fähigkeit eines Programms, seine eigene Struktur zur Laufzeit zu untersuchen und zu manipulieren. Java hat sie, C# hat sie, Python hat sie. In Delphi heißt das Feature RTTI, und System.Rtti ist die moderne, objektorientierte API dafür.
Normalerweise ist Ihr Code spezifisch: Um den Name eines TCustomer zu lesen, schreiben Sie Customer.Name, und der Compiler backt exakt ein, wo dieses Feld liegt. RTTI dreht das um. Es erlaubt Ihnen, Code zu schreiben, der mit einem Typ arbeitet, über den er bis zur Laufzeit nichts weiß — indem er den Typ bittet, sich selbst zu beschreiben, und dann auf die Antwort reagiert.
Das Diagramm bringt den wesentlichen Unterschied auf den Punkt. Links ist der Property-Name Name einkompiliert — überlegen Sie es sich anders, ändern Sie den Quelltext und kompilieren neu. Rechts ist 'Name' ein String — Daten, die Ihr Programm aus einer Konfigurationsdatei, einem JSON-Schlüssel oder einer Datenbankspalte erhalten kann, und auf die es reagiert, ohne je von TCustomer gehört zu haben. Diese Indirektion ist die gesamte Superkraft.
Warum sollte ich das jemals wollen?
Die ehrliche Frage. Reflection hat einen Preis (wir werden weiter unten offen darüber sprechen), also muss sie sich ihren Platz verdienen. Die Aufgaben, bei denen sie das tut, haben überwiegend eine gemeinsame Form: Sie schreiben Code, der einheitlich über viele Typen funktionieren muss, die Sie nicht kontrollieren. Ein paar konkrete Beispiele, jedes davon etwas, das Menschen tatsächlich mit System.Rtti bauen:
- Serialisierung — ein beliebiges Objekt in JSON/XML verwandeln und zurück, indem man seine Properties durchläuft. Die RTL-eigene Unit
REST.Jsonmacht genau das. - Objektrelationales Mapping — auslesen, welche Felder eine Klasse hat, um
INSERT-/SELECT-Anweisungen automatisch zu erzeugen. - Dependency Injection & IoC-Container — den Konstruktor einer Klasse inspizieren, um herauszufinden, was zu übergeben ist.
- Validierung & UI-Generierung — Properties (und Attribute darauf) lesen, um ein Formular automatisch aufzubauen oder eine Regel zu prüfen.
- Objekte generisch kopieren / vergleichen — eine „Klone ein beliebiges Objekt"-Routine, der egal ist, was das Objekt ist.
Der rote Faden: Ohne RTTI zwingt Sie jede dieser Aufgaben zu handgeschriebenem Boilerplate pro Klasse — eine ToJSON-Methode auf jeder einzelnen Klasse, für immer. Mit RTTI schreiben Sie den Walker einmal.
Der Startpunkt: TRttiContext
Alles in System.Rtti beginnt mit einem einzigen Record: TRttiContext. Stellen Sie ihn sich als Ihre Sitzung mit dem Reflection-System vor — Sie öffnen eine, stellen Fragen und schließen sie. Er ist im Quelltext als Record mit Create und Free deklariert:
uses
System.Rtti;
var
Ctx: TRttiContext;
Typ: TRttiType;
begin
Ctx := TRttiContext.Create;
try
Typ := Ctx.GetType(TButton); // die Klasse TButton reflektieren
ShowMessage(Typ.QualifiedName); // 'Vcl.StdCtrls.TButton'
finally
Ctx.Free;
end;
end;Ein paar Dinge sind gleich hier hervorhebenswert, denn die Lebensdauerregeln sind etwas ungewöhnlich und sollten vom ersten Beispiel an richtig sitzen.
Die meistgenutzte Methode des Contexts ist GetType: Sie nimmt entweder eine Klassenreferenz oder einen Type-Info-Zeiger und liefert einen TRttiType — die Laufzeitbeschreibung dieses Typs. Dieser TRttiType ist das Tor zu allem in Teil 2: seinen Properties, Methoden, Feldern und Attributen. GetType hat einen Partner, GetTypes, der jeden Typ zurückgibt, für den das Programm RTTI besitzt, und FindType schlägt einen Typ über seinen qualifizierten Namen als String nach.
Der universelle Wert: TValue
Es gibt einen zweiten Record, den Sie kennen müssen, bevor irgendetwas anderes Sinn ergibt, denn er ist überall in der API: TValue. Das Problem, das er löst: Wenn Sie eine Property lesen, deren Typ Sie zur Compile-Zeit nicht kennen — was liefert dieser Lesevorgang dann zurück? Einen Integer? Einen string? Ein TObject? Das können Sie erst zur Laufzeit wissen — RTTI braucht also einen Container, der einen Wert beliebigen Typs aufnehmen kann und sich dabei merkt, welcher Typ das tatsächlich ist. Dieser Container ist TValue.
Das Bild ist das Konzept: Was immer Sie hineinlegen — einen Integer, einen String, eine Objektreferenz — kommt als TValue heraus, das seinen eigenen Typ kennt, und Sie holen es mit einem typisierten Accessor wieder heraus. Hinein geht es mit TValue.From<T> (oder einer einfachen Zuweisung, dank impliziter Operatoren, die der Quelltext für gängige Typen deklariert), heraus mit AsInteger, AsString, AsObject, dem generischen AsType<T> oder dem sicheren ToString:
var
V: TValue;
begin
V := TValue.From<Integer>(42); // einen Integer einpacken
// ... oder einfach: V := 42; // der implizite Operator tut dasselbe
if V.IsEmpty then Exit;
ShowMessage(V.AsString); // '42' (Konvertierung im ToString-Stil)
ShowMessage(V.AsInteger.ToString);// '42' (typisierte Extraktion)
end;TValue ist das, was eine einzige Serialisierungsroutine eine Property verarbeiten lässt, egal ob sie eine Zahl, ein Name oder ein verschachteltes Objekt ist — der „Beliebiger-Typ"-Klebstoff, der generischen Code erst möglich macht.
Die andere Seite: Wann Sie RTTI nicht verwenden sollten
Ein Versprechen an die Leserschaft — Reflection ist mächtig, und genau deshalb wird sie leicht überstrapaziert. Lassen Sie mich das ehrlich einordnen.
RTTI tauscht ein wenig Laufzeitkosten und etwas Compile-Zeit-Sicherheit gegen enorme Flexibilität. Dieser Tausch ist ein Schnäppchen, wenn Sie eine generische Framework-Komponente schreiben (einen Serializer, einen Mapper), die über viele Typen hinweg genutzt wird. Er ist ein schlechtes Geschäft, wenn Sie damit etwas erledigen wollen, das Sie direkt tun könnten.
Was Teil 1 festgehalten hat
Wir wollten beantworten, was RTTI ist und warum man es haben will — ehrlich eingeordnet. Das Fundament für Teil 2:
- RTTI (Run-Time Type Information) ist Delphis Reflection: Code, der Typen untersucht und manipuliert, die er zur Compile-Zeit nicht kannte. Sie treibt Serializer, ORMs, DI-Container und den Object Inspector selbst an.
- Alles beginnt mit dem Record
TRttiContext— perCreateanlegen, perGetTypenach einemTRttiTypefragen, am EndeFreeaufrufen. Die zurückgegebenenTRttiType-Objekte gehören dem Context. TValueist der universelle typisierte Container, mit dem generischer Code einen Wert beliebigen Typs halten und weiterreichen kann, ohne zu vergessen, welcher Typ es ist.- Reflektieren Sie dort, wo sich Flexibilität lohnt — in typübergreifendem Framework-Code. Nutzen Sie direkten Member-Zugriff, wenn Sie den Typ bereits kennen. Cachen Sie reflektierte Lookups; reflektieren Sie nicht in heißen Schleifen.
RTTI lässt Ihr Programm Typen selbst als Daten behandeln — es entdeckt die Properties und Methoden eines Objekts zur Laufzeit und agiert darauf, ohne eine einzige Zeile Code, die für genau diese Klasse geschrieben wurde.
Mit dem „Was" und „Warum" im Gepäck wird Teil 2 konkret: einen TRttiType öffnen, seine Properties und Felder durchlaufen, Werte darüber lesen und schreiben, und die Custom Attributes lesen, die moderne Delphi-Frameworks so deklarativ machen. Dort beginnt Reflection, sich wie Magie anzufühlen. Bis dahin!