Wir haben einen weiten Weg zurückgelegt. Teil 1 hat erklärt, was RTTI ist — Reflection, also Code, der Typen inspiziert, die er zur Compile-Zeit nicht kannte — und TRttiContext und TValue eingeführt. Teil 2 hat die Properties, Felder und Attribute eines Objekts gelesen und Werte per Name mit GetValue/SetValue zurückgeschrieben. Ein Verb fehlt noch, und es ist das spektakulärste: eine Methode über ihren Namen aufrufen — als String.
Wenn sich das Lesen einer Property per Name wie ein kleiner Zaubertrick anfühlte, ist das Aufrufen einer Methode per Name die ganze Show. Es bedeutet, dass Sie DoThing auf einem Objekt aufrufen können, wenn der String 'DoThing' erst zur Laufzeit eintrifft — aus einem Skript, einer Konfigurationsdatei, einer Nachricht von einem Server, einem Plugin. Genau so funktionieren Command-Dispatcher, Scripting-Brücken und Test-Runner.
Teil 3 behandelt die Invocation — TRttiMethod.Invoke, das Übergeben von Argumenten und das Auslesen von Rückgabewerten als TValue — und dann, um die ganze Serie abzurunden, bauen wir mit allem Gelernten ein kleines, wirklich nützliches Werkzeug: einen generischen Objekt-zu-CSV-Exporter, der mit jeder Liste jeder Klasse funktioniert — allein mit Reflection. Bringen wir es stark zu Ende.
Eine Methode über ihren Namen aufrufen
Der Aufbau spiegelt Teil 2 exakt wider: Context öffnen, den Typ holen — aber diesmal fragen wir nach einer Methode und rufen sie per Invoke auf. Angenommen, wir haben eine Klasse mit einer Methode, die etwas tut und ein Ergebnis zurückgibt:
type
TCalculator = class
public
function Add(A, B: Integer): Integer;
procedure Reset;
end;So rufen Sie Add(2, 3) auf, wenn 'Add' ein Laufzeit-String ist — beachten Sie, dass die Argumente als Array von TValue hineingehen und das Ergebnis als TValue zurückkommt:
uses
System.Rtti;
var
Ctx: TRttiContext;
Calc: TCalculator;
Method: TRttiMethod;
Res: TValue;
begin
Calc := TCalculator.Create;
Ctx := TRttiContext.Create;
try
Method := Ctx.GetType(TCalculator).GetMethod('Add');
Res := Method.Invoke(Calc, [2, 3]); // Add(2, 3) aufrufen
ShowMessage(Res.AsInteger.ToString); // '5'
finally
Ctx.Free;
Calc.Free;
end;
end;Betrachten Sie die Signatur, die uns die RTL gibt: Invoke(Instance: TObject; const Args: array of TValue): TValue. Jedes Argument ist ein TValue (die Integer 2 und 3 boxen sich selbst über die impliziten Operatoren aus Teil 1), und der Rückgabewert ist ein TValue, den Sie mit AsInteger, AsString oder dem jeweils passenden Accessor auspacken. Bei einer Prozedur wie Reset, die nichts zurückgibt, ignorieren Sie das (leere) Ergebnis einfach.
Das Diagramm ist der gesamte Mechanismus: Ein Methodenname plus geboxte Argumente gehen hinein, die Methode läuft auf Ihrem lebenden Objekt, und ein geboxtes Ergebnis kommt zurück. Ein Command-Dispatcher ist genau dieser Aufruf, bei dem Name und Argumente aus einer eingehenden Nachricht stammen.
Das Finale: ein generischer CSV-Exporter
Setzen wir alle drei Teile für etwas ein, das Sie tatsächlich ausliefern könnten. Das Ziel: jede TObjectList<T> nach CSV exportieren — Kopfzeile aus den Property-Namen, eine Zeile pro Objekt — mit einer einzigen Routine, die nichts über die Klassen weiß, die man ihr übergibt. Das ist genau die Art kleiner Gewinn, für die Reflection perfekt ist.
Wir lassen eine Klasse eine Property sogar per Custom-Attribut vom Export ausnehmen — ein Rückgriff auf Teil 2. Zuerst das Attribut:
type
// Markiert eine Property, die der Exporter überspringen soll (Attribute aus Teil 2)
NoExportAttribute = class(TCustomAttribute);
TEmployee = class
private
FName: string;
FSalary: Currency;
FPasswordHash: string;
public
property Name: string read FName write FName;
property Salary: Currency read FSalary write FSalary;
[NoExport] // niemals exportieren
property PasswordHash: string read FPasswordHash write FPasswordHash;
end;Nun der Exporter. Er läuft einmal über die Properties, um die Kopfzeile zu bauen (und überspringt alles, was mit [NoExport] markiert ist), dann über die Werte jedes Objekts, um die Zeilen zu erzeugen — mit GetProperties, GetValue, HasAttribute<T> und TValue.ToString aus der ganzen Serie:
uses
System.Rtti, System.SysUtils, System.Classes, System.Generics.Collections;
function ExportToCsv<T: class>(const Items: TObjectList<T>): string;
var
Ctx: TRttiContext;
Typ: TRttiType;
Props: TArray<TRttiProperty>;
Prop: TRttiProperty;
Item: T;
Line: TArray<string>;
SB: TStringBuilder;
// Property nur aufnehmen, wenn sie lesbar und nicht mit [NoExport] markiert ist
function Exported(const P: TRttiProperty): Boolean;
begin
Result := P.IsReadable and not P.HasAttribute<NoExportAttribute>;
end;
begin
Ctx := TRttiContext.Create;
SB := TStringBuilder.Create;
try
Typ := Ctx.GetType(TClass(T));
Props := Typ.GetProperties;
// 1. Kopfzeile — Property-Namen
Line := [];
for Prop in Props do
if Exported(Prop) then
Line := Line + [Prop.Name];
SB.AppendLine(string.Join(',', Line));
// 2. Eine Zeile pro Objekt — Property-Werte per Reflection
for Item in Items do
begin
Line := [];
for Prop in Props do
if Exported(Prop) then
Line := Line + [Prop.GetValue(Item).ToString];
SB.AppendLine(string.Join(',', Line));
end;
Result := SB.ToString;
finally
SB.Free;
Ctx.Free;
end;
end;Geben Sie ihm eine Liste von Mitarbeitern, und Sie erhalten:
Name,Salary
Ada Lovelace,95000
Grace Hopper,102000
PasswordHash fehlt — das [NoExport]-Attribut hat seinen Dienst getan — und, entscheidend: Diese Routine exportiert TCustomer, TInvoice oder jede Klasse, die Sie nächstes Jahr schreiben, ohne eine einzige Änderung. Das ist das ganze Versprechen der Reflection, eingelöst in rund vierzig Zeilen: den Walker einmal schreiben, für immer profitieren. Hier der Ablauf auf einen Blick.
Das Diagramm ist das Werkzeug in einer Zeile: Eine Liste unbekannter Objekte geht hinein, der Reflection-Walk macht aus Property-Namen eine Kopfzeile und aus Property-Werten Zeilen, und CSV kommt heraus — nirgendwo klassenspezifischer Code.
Die andere Seite: Ist Reflection hier die richtige Wahl?
Ein Versprechen an die Leserin und den Leser — auch bei einem schönen Werkzeug: Fragen Sie, ob Reflection ihren Platz verdient. Für einen CSV-Exporter, der beliebige Klassen quer durch Ihre Codebasis verarbeiten muss: ja. Die Alternative wäre ein handgeschriebener Exporter pro Klasse — genau der Boilerplate, den Reflection ausradieren soll. Wenn Sie aber nur je eine bekannte Klasse exportieren, ist eine schlichte handgeschriebene Funktion einfacher, schneller und sicherer — kein TValue-Boxing, volle Compile-Zeit-Prüfung. Die Regel aus Teil 1 gilt bis zum Schluss: Reflektieren Sie, wenn Ihr Code viele Typen abdecken muss, die Sie nicht kontrollieren; schreiben Sie direkten Code, wenn Sie den Typ kennen. Der Exporter qualifiziert sich, weil „jede Klasse" schon in seiner Signatur steht.
Fazit — die ganze Serie
Drei Teile haben uns von „Was ist RTTI" zu einem funktionierenden Werkzeug geführt. Das vollständige Bild:
- Eine Methode per Name aufrufen mit
TRttiMethod.Invoke(Instance, [args])— Argumente gehen alsTValuehinein, das Ergebnis kommt alsTValuezurück. Die Overloads erlauben sogar den reflektiven Aufruf von Konstruktoren und Klassenmethoden (das Herz von DI-Containern). - Reflection verliert die Compile-Zeit-Prüfung: Falsche Argumente lösen zur Laufzeit einen
EInvocationErroraus. Sichern Sie Invocations ab, die aus externen Eingaben gebaut werden. - Der Exporter als Finale zeigt den Ertrag:
GetProperties+GetValue+HasAttribute<T>ergeben einen CSV-Writer, der mit jeder Klasse funktioniert, einmal geschrieben — die Essenz jedes Serializers und Mappers. - Der rote Faden aller drei Teile: Reflektieren Sie, wenn Ihr Code Typen abdecken muss, die Sie nicht kontrollieren; schreiben Sie direkten Code, wenn Sie den Typ kennen.
Die Invocation vervollständigt das Trio — Lesen (
GetValue), Schreiben (SetValue) und Aufrufen (Invoke) jedes Members per Name — und macht aus „Typen als Daten" funktionierende Werkzeuge wie einen klassenunabhängigen CSV-Exporter in wenigen Dutzend Zeilen.
Damit schließt die RTTI-Serie. Wenn Sie die Grundlagen noch einmal brauchen: Teil 1 ist das „Was und Warum", und Teil 2 behandelt Properties und Attribute. Sie verstehen jetzt die Maschinerie hinter Delphis Serializern, ORMs und DI-Containern — und Sie können Ihre eigene bauen, wenn der Bedarf wirklich Ihrer ist. Reflektieren Sie mal drüber.