Skip to content

Delphi Spickzettel

Object Pascal-Dialekt für schnelle Anwendungsentwicklung.

01

Programmstruktur & Grundlagen

Programmstruktur & Units

Ein Delphi-Programm beginnt mit 'program' und endet mit 'end.' (Punkt). {$APPTYPE CONSOLE} ist eine Compiler-Direktive, die es als Konsolen-App markiert. 'uses' importiert Units (Module) — System.SysUtils hat Format, IntToStr usw. Units haben einen interface-Abschnitt (öffentliche Deklarationen) und einen implementation-Abschnitt (Code). WriteLn gibt Text mit Zeilenumbruch aus; Write ohne. ReadLn liest Eingabe (oder pausiert). Der begin..end-Hauptblock ist der Einstiegspunkt des Programms.

delphi
program Demo;          // program keyword starts a console app

{$APPTYPE CONSOLE}      // compiler directive: console application

uses                    // import units (like #include or import)
  System.SysUtils,      // utilities (Format, IntToStr, etc.)
  System.Classes;       // TList, TStrings, etc.

// a unit has interface (declarations) and implementation (bodies)
// unit Math;
// interface
//   function Add(A, B: Integer): Integer;
// implementation
//   function Add(A, B: Integer): Integer;
//   begin Result := A + B; end;
// end.

var
  Name: string;
begin
  Name := 'Alice';
  WriteLn('Hello, ', Name, '!');    // WriteLn = output with newline
  Write('No newline');              // Write = output without newline
  ReadLn;                           // wait for Enter (pause)
end.

Variablen, Typen & Konstanten

Delphi ist stark typisiert. Häufige Typen: Integer (32-Bit), Int64 (64-Bit), Double (64-Bit Float), Extended (80-Bit Float auf x86), Single (32-Bit Float), string (Unicode, referenzzählend), Char (WideChar, 2 Bytes), Boolean, Byte (0-255). TDateTime ist eigentlich ein Double (Tage seit 1899-12-30). Konstanten verwenden 'const' — typisierte Konstanten haben einen Typ, untypisierte sind flexibel. Subrange-Typen (0..150) schränken Werte ein. Aufzählungen (TDay) definieren benannte Konstanten. Format() ist wie sprintf: %s (String), %d (Integer), %f (Float).

delphi
var
  Name: string = 'Alice';        // string (managed, reference-counted)
  Age: Integer = 30;             // 32-bit signed integer
  BigNum: Int64 = 9223372036854775807;  // 64-bit
  Pi: Double = 3.14159;          // 64-bit float (IEEE 754)
  E: Extended = 2.71828;         // 80-bit float (x87)
  Rate: Single = 0.5;            // 32-bit float
  Ch: Char = 'A';                // 2-byte Unicode char (WideChar)
  IsDev: Boolean = True;         // True/False
  Bytes: Byte = 255;             // 0..255
  Date: TDateTime;               // date/time (double)

// constants
const
  MaxRetries = 3;                // untyped constant
  Pi: Double = 3.14159265;       // typed constant
  Greeting: string = 'Hello';

// type aliases
type
  TAge = 0..150;                 // subrange type
  TDay = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);  // enumeration

begin
  Date := Now;                   // current date/time
  WriteLn(Format('%s is %d', [Name, Age]));   // formatted output
  WriteLn('Pi = ', Pi:0:2);      // 3.14 (width:decimals)
end;

Operatoren & Ausdrücke

Delphi verwendet := für Zuweisung und = für Gleichheit (entgegengesetzt zu C-ähnlichen Sprachen). div ist Ganzzahldivision; / ist reale Division (gibt immer Extended/Double zurück). mod ist der Rest. and/or/not/xor arbeiten sowohl mit Booleans (logisch) als auch mit Integern (bitweise) — der Kontext bestimmt dies. shl/shr sind Bitverschiebungen. Inc/Dec sind effiziente In-Place-Inkrementierungen/Dekrementierungen (vermeiden Sie A := A + 1 zu schreiben). String-Verkettung verwendet +. Power() ist in System.Math. Die := vs = Unterscheidung ist die #1-Quelle für Anfängerfehler.

delphi
var
  A, B: Integer;
  X, Y: Double;
  S1, S2: string;
begin
  A := 10; B := 3;
  WriteLn(A + B);    // 13 (addition)
  WriteLn(A - B);    // 7
  WriteLn(A * B);    // 30
  WriteLn(A div B);  // 3 (integer division)
  WriteLn(A mod B);  // 1 (remainder)
  WriteLn(A / B);    // 3.33 (real division, always Extended)

  X := 2.0; Y := 3.0;
  WriteLn(X + Y);    // 5.0
  WriteLn(Power(X, Y)); // 8.0 (needs System.Math)

  // comparison (return Boolean)
  WriteLn(A > B);    // TRUE
  WriteLn(A = B);    // FALSE (= is equality, not assignment!)
  WriteLn(A <> B);   // TRUE (not equal)
  WriteLn(A >= B);   // TRUE

  // logical operators
  WriteLn(True and False);  // FALSE
  WriteLn(True or False);   // TRUE
  WriteLn(not True);        // FALSE
  WriteLn(True xor False);  // TRUE

  // string concatenation
  S1 := 'Hello'; S2 := 'World';
  WriteLn(S1 + ', ' + S2 + '!');  // Hello, World!

  // bitwise (on integers)
  WriteLn(5 and 3);  // 1
  WriteLn(5 or 3);   // 7
  WriteLn(5 shl 1);  // 10 (shift left = *2)
  WriteLn(5 shr 1);  // 2 (shift right = /2)

  // Inc and Dec (modify in place)
  Inc(A);      // A := A + 1
  Inc(A, 5);   // A := A + 5
  Dec(A);      // A := A - 1
end;

Eingabe, Ausgabe & Formatierung

Format() ist Delphis sprintf — verwendet %s (String), %d (Integer), %f (Float), %x (Hex), %m (Währung), mit Breiten-/Präzisionsmodifikatoren. WriteLn(value:width:decimals) formatiert Floats direkt. ReadLn liest Eingabe in eine Variable. StrToInt/StrToFloat konvertieren Strings in Zahlen (werfen EConvertError bei Fehlern); TryStrToInt gibt einen Boolean zurück und ist sicherer. IntToStr/FloatToStr konvertieren Zahlen in Strings. FormatDateTime formatiert Daten (yyyy, mm, dd, hh, nn, ss). FloatToStrF gibt präzise Kontrolle (ffFixed, ffCurrency, ffExponent).

delphi
var
  Name: string;
  Age: Integer;
  Salary: Double;
begin
  // console output
  WriteLn('Hello, World!');        // with newline
  Write('No newline');             // without
  WriteLn;                         // just a newline

  // formatted output with Format (like sprintf)
  WriteLn(Format('Name: %s, Age: %d', ['Alice', 30]));
  WriteLn(Format('Pi: %.4f', [3.14159]));      // Pi: 3.1416
  WriteLn(Format('Hex: %x', [255]));            // Hex: FF
  WriteLn(Format('Pad: %10d', [42]));           // right-aligned
  WriteLn(Format('Left: %-10d|', [42]));        // left-aligned
  WriteLn(Format('Money: %m', [1234.56]));      // currency

  // WriteLn with format specifiers (width:decimals)
  WriteLn(3.14159:0:2);    // 3.14
  WriteLn(42:5);           //    42 (width 5)

  // console input
  Write('Enter your name: ');
  ReadLn(Name);
  Write('Enter your age: ');
  ReadLn(Age);

  // type conversion functions
  Salary := StrToFloat('50000.50');
  Age := StrToInt('30');
  WriteLn(IntToStr(42));              // '42'
  WriteLn(FloatToStr(3.14));          // '3.14'
  WriteLn(FloatToStrF(3.14159, ffFixed, 8, 2));  // '3.14'
  WriteLn(FormatDateTime('yyyy-mm-dd', Now));    // '2024-06-18'

  // TryStrToInt (safe parsing)
  if TryStrToInt('123', Age) then
    WriteLn('Parsed: ', Age);
end;

Units, Gültigkeit & Sichtbarkeit

Units sind Delphis Module. Der interface-Abschnitt deklariert, was öffentlich ist (für Benutzer sichtbar); implementation enthält den Code und kann private Typen/Variablen haben. initialization/finalization-Abschnitte laufen beim Laden/Entladen der Unit (wie Konstruktoren/Destruktoren für die Unit). Im interface deklarierte Variablen sind global; in implementation sind sie unit-privat. Typen im interface sind öffentlich; in implementation privat. Dieses Zwei-Abschnitts-Design erzwingt Kapselung auf Unit-Ebene. Die 'uses'-Klausel importiert andere Units — lösen Sie Namenskonflikte mit UnitName.Identifier.

delphi
// Unit declaration
unit MathHelper;

interface

uses
  System.SysUtils;

// public types (visible to users of the unit)
type
  TCalculator = class
  public
    function Add(A, B: Integer): Integer;
  end;

// public constants
const
  Pi = 3.14159265358979;

// public variables
var
  Counter: Integer;

// public function declarations
function Multiply(A, B: Integer): Integer;

implementation

// private types (only visible inside this unit's implementation)
type
  TInternal = record
    Value: Integer;
  end;

// private variables
var
  InternalCount: Integer;

// function body (implementation)
function Multiply(A, B: Integer): Integer;
begin
  Result := A * B;
end;

function TCalculator.Add(A, B: Integer): Integer;
begin
  Result := A + B;
end;

initialization
  // runs when the unit loads
  Counter := 0;
  InternalCount := 0;

finalization
  // runs when the unit unloads (cleanup)
  // free resources here

end.
02

Kontrollfluss

If...Then...Else

If...Then...Else ist Delphis Bedingung. KRITISCH: kein Semikolon vor 'else' — das Semikolon beendet die Anweisung, und else ist Teil des if. Für Mehranweisungs-Zweige umschließen Sie mit begin..end (immer noch kein Semikolon vor else). and/or/not sind logische Operatoren (auch bitweise auf Integern). Verwenden Sie Klammern zum Gruppieren von Bedingungen: (A > 0) and (B > 0). Die fehlende-Semikolon-vor-else-Regel ist der häufigste Delphi-Syntaxfehler für Anfänger.

delphi
var
  Score: Integer;
  Grade: string;
begin
  Score := 85;

  // simple if
  if Score >= 60 then
    WriteLn('Pass');

  // if-else (no semicolon before 'else'!)
  if Score >= 90 then
    Grade := 'A'
  else if Score >= 80 then
    Grade := 'B'
  else if Score >= 70 then
    Grade := 'C'
  else
    Grade := 'F';

  WriteLn('Grade: ', Grade);

  // multi-statement if (needs begin..end)
  if Score > 50 then
  begin
    WriteLn('Passed');
    WriteLn('Congratulations');
  end
  else
  begin
    WriteLn('Failed');
    WriteLn('Try again');
  end;

  // nested with logical operators
  if (Score >= 0) and (Score <= 100) then
    WriteLn('Valid score');
end;

Case (Switch)-Anweisung

Case ist Delphis Switch — funktioniert mit ordinalen Typen (Integer, Char, Aufzählung, Subrange). Jeder Zweig kann ein einzelner Wert, eine kommagetrennte Liste ('D', 'F') oder ein Bereich (1..5) sein. Die else-Klausel ist der Standard. Case fällt NICHT durch (im Gegensatz zu C). Für Mehranweisungs-Zweige verwenden Sie begin..end. Case ist sauberer als verkettete if-else für diskrete Werte. Sie können nicht direkt auf Strings case-anwenden (verwenden Sie if-else oder eine Nachschlagetabelle).

delphi
var
  Grade: Char;
  Day: Integer;
begin
  Grade := 'B';

  // case on ordinal (integer, char, enum)
  case Grade of
    'A': WriteLn('Excellent');
    'B': WriteLn('Good');
    'C': WriteLn('Average');
    'D', 'F': WriteLn('Poor');     // multiple values
  else
    WriteLn('Invalid grade');       // default (else clause)
  end;

  // case with ranges
  Day := 3;
  case Day of
    1..5: WriteLn('Weekday');       // range
    6, 7: WriteLn('Weekend');
  else
    WriteLn('Invalid day');
  end;

  // case with enums
  type TColor = (Red, Green, Blue);
  var C: TColor;
  C := Green;
  case C of
    Red: WriteLn('Stop');
    Green: WriteLn('Go');
    Blue: WriteLn('Relax');
  end;

  // case with multi-statement branches
  case Grade of
    'A': begin
           WriteLn('Excellent');
           WriteLn('Keep it up');
         end;
    'B': WriteLn('Good');
  end;
end;

For-Schleifen (To, Downto, In)

For...to iteriert aufsteigend; For...downto absteigend. Die Schleifenvariable kann nicht innerhalb der Schleife modifiziert werden. For...in (modernes Delphi) iteriert Arrays, Strings (Zeichen für Zeichen), Sets und alles Aufzählbare. Break verlässt die Schleife; Continue springt zur nächsten Iteration. Es gibt keinen eingebauten Schritt — verwenden Sie eine Bedingung oder eine While-Schleife. Die Schleifenvariable ist nach der Schleife undefiniert (verlassen Sie sich nicht auf ihren Wert). For...in wird für Collections bevorzugt (sauberer, keine Indexfehler).

delphi
var
  i: Integer;
  Fruits: array of string;
  S: string;
begin
  // for...to (ascending)
  for i := 1 to 5 do
    WriteLn(i);          // 1 2 3 4 5

  // for...downto (descending)
  for i := 5 downto 1 do
    WriteLn(i);          // 5 4 3 2 1

  // for with step (no built-in step — use a while or compute)
  for i := 0 to 9 do
    if i mod 2 = 0 then
      WriteLn(i);        // 0 2 4 6 8

  // nested loops
  for i := 1 to 3 do
    for j := 1 to 3 do
      Write(i * j, ' ');
  WriteLn;

  // for...in (iterates collections, modern Delphi)
  Fruits := ['apple', 'banana', 'cherry'];
  for S in Fruits do
    WriteLn(S);

  // for...in on string (iterates characters)
  for S in 'Hello' do
    Write(S, ' ');       // H e l l o
  WriteLn;

  // for...in on set
  type TDigits = set of 1..5;
  var D: TDigits := [1, 3, 5];
  for i in D do
    WriteLn(i);          // 1 3 5

  // Break and Continue
  for i := 1 to 100 do
  begin
    if i > 10 then Break;       // exit loop
    if i mod 2 = 0 then Continue; // skip to next iteration
    WriteLn(i);                  // 1 3 5 7 9
  end;
end;

While & Repeat...Until

While testet vor dem Körper (kann nie ausführen); repeat...until testet danach (läuft immer mindestens einmal). KRITISCH: while läuft weiter, während die Bedingung TRUE ist; repeat stoppt, wenn die Bedingung TRUE ist (entgegengesetzte Logik!). repeat...until benötigt kein begin..end (es ist inhärent ein Block). Verwenden Sie while für 'null oder mehr Mal' und repeat für 'ein oder mehr Mal'. Break verlässt; Continue springt zum Test. while True mit Break ist ein häufiges Idiom für Schleifen mit komplexen Exit-Bedingungen.

delphi
var
  Count: Integer;
  Line: string;
begin
  // while: test BEFORE (may never run)
  Count := 0;
  while Count < 3 do
  begin
    WriteLn(Count);
    Inc(Count);
  end;
  // 0 1 2

  // repeat...until: test AFTER (runs at least once)
  Count := 0;
  repeat
    WriteLn(Count);
    Inc(Count);
  until Count >= 3;
  // 0 1 2

  // KEY DIFFERENCE: while continues while TRUE; until stops when TRUE
  // while X < 3 do ...  ==  repeat ... until X >= 3

  // repeat doesn't need begin..end (it's already a block)
  Count := 5;
  repeat
    WriteLn(Count);
    Dec(Count);
  until Count = 0;

  // reading input until a condition
  repeat
    Write('Enter "quit" to stop: ');
    ReadLn(Line);
  until (Line = 'quit') or (Line = 'exit');

  // infinite loop with Break
  while True do
  begin
    WriteLn('Running...');
    if SomeCondition then Break;
  end;

  // Continue in while
  Count := 0;
  while Count < 10 do
  begin
    Inc(Count);
    if Count mod 2 = 0 then Continue;
    WriteLn(Count);   // 1 3 5 7 9
  end;
end;

With...Do & Goto

With...Do greift auf Member eines Records/Objekts zu, ohne die Variable zu wiederholen — nützlich für Initialisierung und Reduzierung von Ausführlichkeit. Vermeiden Sie verschachtelte With (Mehrdeutigkeit, zu welchem Objekt ein Member gehört). Goto springt zu einem Label — selten in modernem Delphi verwendet (bevorzugen Sie Break/Continue/Exit); deklarieren Sie Labels mit 'label'. Exit verlässt die Prozedur sofort; Exit(value) gibt einen Wert aus einer Funktion zurück (moderne Syntax). With kann Code weniger lesbar machen, wenn überstrapaziert — verwenden Sie es sparsam für einfache Fälle.

delphi
type
  TPerson = record
    Name: string;
    Age: Integer;
    Email: string;
  end;

var
  P: TPerson;
  i: Integer;
label
  RetryPoint;   // declare a label for Goto
begin
  // With...Do: access record/object members without repeating the name
  with P do
  begin
    Name := 'Alice';
    Age := 30;
    Email := '[email protected]';
    WriteLn(Name, ' is ', Age);   // instead of P.Name, P.Age
  end;

  // With on a function result
  with TStringList.Create do
  try
    Add('line 1');
    Add('line 2');
    SaveToFile('output.txt');
  finally
    Free;
  end;

  // nested With (avoid — ambiguous)
  with P, TStringList.Create do
  try
    Add(Name);   // Name could be P.Name or TStringList.Name
  finally
    Free;
  end;

  // Goto (rarely used — prefer structured alternatives)
  i := 0;
RetryPoint:
  Inc(i);
  WriteLn('Attempt ', i);
  if i < 3 then
    Goto RetryPoint;
  WriteLn('Done after ', i, ' attempts');

  // Exit: leave the current procedure/function
  if P.Age < 0 then
  begin
    WriteLn('Invalid age');
    Exit;          // return immediately
  end;

  // Exit with a value (for functions)
  // Exit(42);  // returns 42 from the function
end;
03

Strings & Textverarbeitung

String-Typen & Operationen

Delphis Standard-String ist UnicodeString (UTF-16, referenzzählend, Copy-on-Write). Strings sind 1-INDIZIERT (S[1] ist das erste Zeichen) — eine häufige Quelle von Fehlern für C-Programmierer. Length() gibt die Zeichenanzahl zurück. Pos() findet einen Teilstring (gibt 0 zurück, wenn nicht gefunden, nicht -1). Copy() extrahiert einen Teilstring (Start, Anzahl). StringReplace ersetzt (rfReplaceAll für alle Vorkommen). Trim/TrimLeft/TrimRight entfernen Leerzeichen. Split/Join sind moderne Methoden (TArray<string>). Verwenden Sie SameText für case-insensitiven Vergleich.

delphi
var
  S: string;           // UnicodeString (default, UTF-16, reference-counted)
  A: AnsiString;       // 8-bit string (legacy, codepage-aware)
  W: WideString;       // COM-compatible (not reference-counted)
  SB: StringBuilder;   // mutable, for heavy concatenation
begin
  S := 'Hello, World';

  // length and indexing (1-indexed!)
  WriteLn(Length(S));        // 12
  WriteLn(S[1]);             // 'H' (first char — 1-indexed!)
  WriteLn(S[Length(S)]);     // 'd' (last char)

  // case conversion
  WriteLn(UpperCase(S));     // HELLO, WORLD
  WriteLn(LowerCase(S));     // hello, world

  // searching
  WriteLn(Pos('World', S));  // 8 (1-indexed position, 0 if not found)
  WriteLn(Pos('xyz', S));    // 0

  // substring
  WriteLn(Copy(S, 1, 5));    // 'Hello' (Start, Length)
  WriteLn(Copy(S, 8, 5));    // 'World'

  // modify (creates new string — strings are immutable-ish)
  S := StringReplace(S, 'World', 'Delphi', [rfReplaceAll]);
  WriteLn(S);                // Hello, Delphi

  // trim
  WriteLn(Trim('  hi  '));        // 'hi'
  WriteLn(TrimLeft('  hi  '));    // 'hi  '
  WriteLn(TrimRight('  hi  '));   // '  hi'

  // split
  var Parts: TArray<string>;
  Parts := 'a,b,c'.Split([',']);
  WriteLn(Length(Parts));    // 3

  // join
  WriteLn(string.Join('-', Parts));  // 'a-b-c'

  // comparison
  WriteLn('abc' = 'abc');    // TRUE (case-sensitive)
  WriteLn(AnsiCompareText('ABC', 'abc'));  // 0 (case-insensitive)
  WriteLn(SameText('ABC', 'abc'));         // TRUE
end;

String-Formatierung & Konvertierung

Format() ist Delphis sprintf: %d (Integer), %f (Float), %s (String), %x (Hex), %m (Währung), mit Breiten-/Präzisionsmodifikatoren. FloatToStrF gibt präzise Kontrolle (ffFixed, ffCurrency, ffNumber, ffExponent). FormatDateTime formatiert Daten: yyyy (4-stelliges Jahr), mm (Monat), dd (Tag), hh (Stunde), nn (Minute), ss (Sekunde), dddd (voller Tagesname), mmmm (voller Monatsname). StrToInt/StrToFloat werfen EConvertError bei ungültiger Eingabe; TryStrToInt gibt einen Boolean zurück (sicherer). Verwenden Sie immer Try... für Benutzereingaben.

delphi
var
  N: Integer := 42;
  F: Double := 3.14159;
  S: string;
  D: TDateTime := Now;
begin
  // Format (like sprintf)
  S := Format('Integer: %d', [N]);              // 'Integer: 42'
  S := Format('Float: %f', [F]);                // 'Float: 3.14'
  S := Format('Float: %.4f', [F]);              // 'Float: 3.1416'
  S := Format('Hex: %x', [N]);                  // 'Hex: 2a'
  S := Format('String: %s', ['Hello']);         // 'String: Hello'
  S := Format('Padded: %10d', [N]);             // '        42'
  S := Format('Left: %-10d|', [N]);             // '42        |'
  S := Format('Multiple: %s=%d, %.2f', ['x', N, F]);

  // FloatToStrF (precise float formatting)
  S := FloatToStrF(F, ffFixed, 8, 2);           // '3.14'
  S := FloatToStrF(F, ffCurrency, 8, 2);        // '$3.14'
  S := FloatToStrF(1234567, ffNumber, 10, 0);   // '1,234,567'

  // date/time formatting
  S := FormatDateTime('yyyy-mm-dd', D);         // '2024-06-18'
  S := FormatDateTime('hh:nn:ss', D);           // '14:30:00'
  S := FormatDateTime('dddd, mmmm d, yyyy', D); // 'Tuesday, June 18, 2024'

  // string to number (throws on invalid)
  N := StrToInt('123');
  F := StrToFloat('3.14');
  D := StrToDateTime('2024-06-18');

  // safe parsing (TryStrTo...)
  if TryStrToInt('123', N) then
    WriteLn('Parsed: ', N);
  if TryStrToInt('abc', N) then
    WriteLn('Valid')
  else
    WriteLn('Invalid number');

  // IntToStr, FloatToStr
  WriteLn(IntToStr(42));
  WriteLn(FloatToStr(3.14));
end;

StringBuilder & TStringList

StringBuilder (veränderbar) ist effizient für Schleifen, die große Strings aufbauen — Append modifiziert an Ort und Stelle, anstatt neue Strings zu erstellen. TStringList ist Delphis Schweizer Taschenmesser: eine Liste von Strings, die sortieren, suchen, Schlüssel=Wert-Paare halten (Values[]), Dateien laden/speichern (eine Zeile pro Element) und begrenzten Text aufteilen kann (CommaText, DelimitedText). TStringList ist 0-indiziert (SL[0]) im Gegensatz zu Strings (S[1]). Umschließen Sie immer mit try..finally, um Free aufzurufen. Es ist die häufigste Methode, Textdateien und einfache Konfigurationen in Delphi zu handhaben.

delphi
uses
  System.SysUtils, System.Classes;

var
  sb: StringBuilder;
  SL: TStringList;
  i: Integer;
begin
  // StringBuilder: efficient concatenation (mutable)
  sb := StringBuilder.Create;
  try
    for i := 1 to 1000 do
      sb.Append('Line ').Append(i).AppendLine;  // chainable
    WriteLn(sb.ToString);
  finally
    sb.Free;
  end;

  // TStringList: versatile string collection
  SL := TStringList.Create;
  try
    // add items
    SL.Add('apple');
    SL.Add('banana');
    SL.Add('cherry');
    WriteLn(SL.Count);          // 3
    WriteLn(SL[0]);             // 'apple' (0-indexed!)

    // sort and find
    SL.Sort;
    SL.Sorted := True;          // auto-sort on Add
    idx := SL.IndexOf('banana');  // find (returns -1 if not found)

    // comma-separated text
    SL.CommaText := 'red,green,blue';   // split into items
    WriteLn(SL.CommaText);               // 'blue,green,red'

    // key=value pairs
    SL.Clear;
    SL.Values['name'] := 'Alice';
    SL.Values['age'] := '30';
    WriteLn(SL.Values['name']);  // 'Alice'

    // file I/O (one line per item)
    SL.SaveToFile('items.txt');
    SL.LoadFromFile('items.txt');

    // delimited text
    SL.Delimiter := ';';
    SL.DelimitedText := 'a;b;c';
  finally
    SL.Free;
  end;
end;

Char-Operationen & Encoding

Char ist ein 2-Byte-Unicode-Zeichen. Ord() gibt den Codepunkt; Char() konvertiert zurück. IsDigit/IsLetter/IsWhiteSpace/IsUpper/IsLower klassifizieren Zeichen. ToUpper/ToLower konvertieren Groß-/Kleinschreibung. TEncoding.UTF8.GetBytes konvertiert Strings in Byte-Arrays (unerlässlich für Datei-E/A und Netzwerke) — UTF-8 verwendet 1-4 Bytes pro Zeichen. TEncoding.Unicode ist UTF-16 LE (immer 2 Bytes/Zeichen). Base64 (TNetEncoding.Base64) kodiert Binärdaten als Text für den Transport. String und Char sind intern UTF-16; konvertieren Sie für Dateispeicherung und Netzwerkprotokolle in UTF-8.

delphi
var
  C: Char;
  S: string;
  Bytes: TBytes;
  i: Integer;
begin
  C := 'A';
  WriteLn(Ord(C));              // 65 (ASCII/Unicode code point)
  WriteLn(Char(66));            // 'B' (code point to char)

  // char classification
  WriteLn(IsDigit('5'));        // TRUE
  WriteLn(IsLetter('A'));       // TRUE
  WriteLn(IsWhiteSpace(' '));   // TRUE
  WriteLn(IsUpper('A'));        // TRUE
  WriteLn(IsLower('a'));        // TRUE

  // case conversion
  WriteLn(ToUpper('a'));        // 'A'
  WriteLn(ToLower('A'));        // 'a'

  // iterate characters
  S := 'Hello';
  for C in S do
    Write(C, '(', Ord(C), ') ');
  WriteLn;   // H(72) e(101) l(108) l(108) o(111)

  // string <-> bytes (encoding)
  Bytes := TEncoding.UTF8.GetBytes('Hello');
  WriteLn(Length(Bytes));       // 5 (ASCII chars are 1 byte in UTF-8)
  Bytes := TEncoding.UTF8.GetBytes('héllo');
  WriteLn(Length(Bytes));       // 6 (é is 2 bytes in UTF-8)

  S := TEncoding.UTF8.GetString(Bytes);  // back to string

  // other encodings
  Bytes := TEncoding.ASCII.GetBytes('Hello');
  Bytes := TEncoding.Unicode.GetBytes('Hello');  // UTF-16 LE (2 bytes/char)

  // Base64 encoding (for binary in text)
  uses System.NetEncoding;
  var B64: string := TNetEncoding.Base64.EncodeBytesToString(Bytes);
  var Decoded: TBytes := TNetEncoding.Base64.DecodeStringToBytes(B64);

  // char arrays
  var Chars: array[0..4] of Char;
  Chars[0] := 'H'; Chars[1] := 'e'; Chars[2] := 'l'; Chars[3] := 'l'; Chars[4] := 'o';
  S := String(Chars);   // convert char array to string
end;

Reguläre Ausdrücke

System.RegularExpressions stellt TRegex für Musterabgleich bereit. IsMatch testet; Match findet das erste; Matches findet alle. Groups erfassen Teile mit Klammern — Zugriff über Groups[1], Groups[2] (1-indiziert). Replace ersetzt Übereinstimmungen ($1, $2 referenzieren Gruppen). Split bricht an einem Muster. Häufige Regex: \d (Ziffer), \w (Wortzeichen), \s (Leerraum), + (eins+), * (null+), {n} (genau n), ^/$ (Start/Ende). roCompiled kompiliert für schnellere wiederholte Verwendung. Validieren Sie immer Benutzereingaben (E-Mails, Telefonnummern) mit Regex.

delphi
uses
  System.RegularExpressions;

var
  Input, Pattern: string;
  Match: TMatch;
  Matches: TMatchCollection;
  Result: string;
begin
  Input := 'Phone: 123-456-7890, Zip: 10001';

  // check if matches
  if TRegEx.IsMatch(Input, 'd{3}-d{3}-d{4}') then
    WriteLn('Found a phone number');

  // find first match
  Match := TRegEx.Match(Input, 'd{5}');
  if Match.Success then
    WriteLn('Zip: ', Match.Value);   // '10001'

  // find all matches
  Matches := TRegEx.Matches(Input, 'd+');
  for Match in Matches do
    WriteLn(Match.Value);   // 123, 456, 7890, 10001

  // capture groups
  Match := TRegEx.Match('2024-06-18', '(d{4})-(d{2})-(d{2})');
  if Match.Success then
  begin
    WriteLn(Match.Groups[1].Value);  // '2024' (year)
    WriteLn(Match.Groups[2].Value);  // '06' (month)
    WriteLn(Match.Groups[3].Value);  // '18' (day)
  end;

  // replace
  Result := TRegEx.Replace(Input, 'd', 'X');
  // 'Phone: XXX-XXX-XXXX, Zip: XXXXX'

  // replace with match reference ($1, $2)
  Result := TRegEx.Replace('John Doe', '(w+) (w+)', '$2, $1');
  // 'Doe, John'

  // split
  var Parts: TArray<string>;
  Parts := TRegEx.Split('a,b;;c', '[,;]+');

  // common patterns
  Pattern := '^[w.-]+@[w.-]+.w+$';   // email
  Pattern := '^https?://[w./-]+$';       // URL
  Pattern := '^d{3}-d{3}-d{4}$';       // US phone

  // compiled regex (faster for repeated use)
  var Regex := TRegEx.Create('d+', [roCompiled]);
  try
    Match := Regex.Match(Input);
  finally
    Regex.Free;
  end;
end;
04

Arrays, Records & Collections

Statische & Dynamische Arrays

Statische Arrays haben eine feste, zur Kompilierzeit festgelegte Größe mit einem benutzerdefinierten Indexbereich (array[0..4] oder array[1..7]). Dynamische Arrays (array of T) sind mit SetLength in der Größe veränderbar — sie sind 0-indiziert und referenzzählend. High() gibt den letzten Index zurück; Length() gibt die Anzahl zurück. SetLength auf einem vorhandenen dynamischen Array ändert die Größe (erhält vorhandene Werte beim Wachsen). Auf nil setzen, um freizugeben. Dynamische Array-Literale verwenden [1, 2, 3]. Mehrdimensionale dynamische Arrays sind 'Arrays von Arrays' (jagged) — jede Zeile kann eine andere Länge haben.

delphi
var
  // static array (fixed size, compile-time)
  Nums: array[0..4] of Integer;        // 5 elements, indices 0..4
  Matrix: array[0..2, 0..2] of Double; // 3x3 2D array
  Days: array[1..7] of string;         // 1-indexed (custom range)

  // dynamic array (resizable at runtime)
  Dyn: array of Integer;
  Dyn2D: array of array of Integer;    // jagged 2D
  i, j: Integer;
begin
  // static array
  Nums[0] := 10;
  Nums[1] := 20;
  for i := 0 to High(Nums) do   // High(Nums) = 4
    WriteLn(Nums[i]);
  WriteLn(Length(Nums));         // 5

  // dynamic array
  SetLength(Dyn, 5);             // allocate 5 elements (0-indexed)
  Dyn[0] := 1;
  Dyn[1] := 2;
  for i := 0 to High(Dyn) do
    WriteLn(Dyn[i]);

  SetLength(Dyn, 10);            // resize (preserves existing values)
  WriteLn(Length(Dyn));          // 10

  Dyn := nil;                    // free memory

  // dynamic array literal (modern Delphi)
  Dyn := [1, 2, 3, 4, 5];
  WriteLn(Length(Dyn));          // 5

  // 2D dynamic array
  SetLength(Dyn2D, 3);           // 3 rows
  for i := 0 to 2 do
  begin
    SetLength(Dyn2D[i], 3);      // 3 cols per row
    for j := 0 to 2 do
      Dyn2D[i][j] := i * 3 + j;
  end;

  // array slice (Open Array)
  WriteLn(Length(Dyn));          // 5
end;

Records (Structs)

Records sind Werttypen (bei Zuweisung kopiert, stack-zugewiesen) — wie Structs in C. Moderne Delphi-Records können Methoden, Eigenschaften und Sichtbarkeit haben (private/public). Records müssen nicht freigegeben werden (keine Heap-Zuweisung). Variant-Records (case...of) erstellen eine Union, in der Felder Speicher teilen — nützlich für Typ-Tags. Verwenden Sie Records für kleine, leichtgewichtige Daten (Punkte, Koordinaten, Konfiguration). Verwenden Sie Klassen für größere Objekte, die Vererbung oder Polymorphismus benötigen. Records sind schneller (keine Heap-Zuweisung), können aber nicht vererbt werden.

delphi
type
  // simple record (like a struct)
  TPoint = record
    X, Y: Integer;
  end;

  // record with methods (modern Delphi)
  TPerson = record
    Name: string;
    Age: Integer;
    // methods
    function Greet: string;
    procedure Birthday;
    // properties
    property IsAdult: Boolean read GetIsAdult;
  private
    function GetIsAdult: Boolean;
  end;

  // variant record (union — fields share memory)
  TValue = record
    case IsInt: Boolean of
      True: (IntVal: Integer);
      False: (FloatVal: Double);
  end;

// implementing record methods
function TPerson.Greet: string;
begin
  Result := 'Hi, I am ' + Name;
end;

procedure TPerson.Birthday;
begin
  Inc(Age);
end;

function TPerson.GetIsAdult: Boolean;
begin
  Result := Age >= 18;
end;

var
  P: TPerson;
  Pt: TPoint;
  V: TValue;
begin
  // record assignment copies all fields (value type)
  P.Name := 'Alice';
  P.Age := 30;
  WriteLn(P.Greet);       // Hi, I am Alice
  P.Birthday;
  WriteLn(P.Age);         // 31
  WriteLn(P.IsAdult);     // TRUE

  // record constructors (modern Delphi)
  P := TPerson.Create;    // zero-initializes
  P.Name := 'Bob';

  // variant record
  V.IsInt := True;
  V.IntVal := 42;
  WriteLn(V.IntVal);      // 42
  V.IsInt := False;
  V.FloatVal := 3.14;
  WriteLn(V.FloatVal);    // 3.14
end;

Sets & Enums

Sets sind Delphis einzigartiges Feature — eine Sammlung von Werten aus einer Aufzählung oder Subrange (max. 256 Elemente). Operatoren: + (Vereinigung), - (Differenz), * (Schnittmenge), = (Gleichheit), <= (Teilmenge), in (Mitgliedschaft). Include/Exclude sind effiziente Einzel-Element-Add/Remove. Sets werden als Bitmaps gespeichert (sehr schnell). Häufige Verwendungen: TFontStyles (fsBold, fsItalic), set of Char für Validierung (['0'..'9']), Wochentage. Enums sind ordinale Typen — iterieren Sie mit Low() bis High(), konvertieren Sie mit GetEnumName in Strings. Sets machen Flag-Kombinationen elegant und typsicher.

delphi
type
  // enumeration
  TDay = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
  TColor = (Red, Green, Blue);

  // set type (collection of enum values)
  TDays = set of TDay;
  TColors = set of TColor;
  TChars = set of Char;     // set of characters

var
  Weekdays: TDays;
  MyColors: TColors;
  D: TDay;
  Digits: TChars;
  C: Char;
begin
  // set operations
  Weekdays := [Mon, Tue, Wed, Thu, Fri];
  MyColors := [Red, Blue];

  // add and remove
  Include(Weekdays, Sat);    // Weekdays := Weekdays + [Sat]
  Exclude(Weekdays, Sun);    // Weekdays := Weekdays - [Sun]

  // set operators
  Weekdays := Weekdays + [Sat];     // union
  Weekdays := Weekdays - [Sat];     // difference
  Weekend := [Sat, Sun];
  if Weekdays * Weekend = [] then   // intersection is empty
    WriteLn('No overlap');

  // membership test
  if Mon in Weekdays then
    WriteLn('Monday is a weekday');

  // iterate enum
  for D := Low(TDay) to High(TDay) do
    WriteLn(D);              // 0 1 2 3 4 5 6 (ord values)

  // convert enum to string
  WriteLn(GetEnumName(TypeInfo(TDay), Ord(Mon)));  // 'Mon'

  // set of Char (common for validation)
  Digits := ['0'..'9'];
  C := '5';
  if C in Digits then
    WriteLn('Is a digit');

  // set comparison
  if MyColors = [Red, Blue] then
    WriteLn('Equal sets');
  if [Red] <= MyColors then     // subset
    WriteLn('Red is included');
end;

TList, TDictionary & Generics

System.Generics.Collections stellt typsichere Collections bereit: TList<T> (dynamisches Array), TDictionary<K,V> (Hash-Map), TQueue<T> (FIFO), TStack<T> (LIFO), THashSet<T> (eindeutige Elemente). Alle sind generisch (Kompilierzeit-Typprüfung, keine Casts). TList hat Add/Remove/Delete/Sort/Contains/IndexOf. TDictionary hat Add/Remove/TryGetValue/Keys/Values. TObjectList<T> besitzt seine Objekte (gibt sie automatisch frei) — verwenden Sie es, wenn die Liste Objektlebenszyklen verwalten soll. Umschließen Sie immer mit try..finally, um Free aufzurufen (dies sind Objekte, keine Records).

delphi
uses
  System.Generics.Collections;

var
  Nums: TList<Integer>;
  Ages: TDictionary<string, Integer>;
  Unique: THashSet<string>;
  Queue: TQueue<string>;
  Stack: TStack<Integer>;
  i: Integer;
  K: string;
  V: Integer;
begin
  // TList<T>: dynamic array (generic)
  Nums := TList<Integer>.Create;
  try
    Nums.Add(1);
    Nums.Add(2);
    Nums.AddRange([3, 4, 5]);
    WriteLn(Nums.Count);          // 5
    WriteLn(Nums[0]);             // 1 (0-indexed)
    Nums[0] := 100;
    Nums.Remove(2);               // by value
    Nums.Delete(0);               // by index
    Nums.Sort;                    // sort in place
    Nums.Reverse;
    if Nums.Contains(3) then
      WriteLn('Found');
    for i in Nums do
      WriteLn(i);
  finally
    Nums.Free;
  end;

  // TDictionary<TKey, TValue>: hash map
  Ages := TDictionary<string, Integer>.Create;
  try
    Ages.Add('Alice', 30);
    Ages.Add('Bob', 25);
    Ages['Eve'] := 28;            // add or update
    if Ages.TryGetValue('Alice', V) then
      WriteLn('Alice is ', V);    // 30
    for K in Ages.Keys do
      WriteLn(K);
    for V in Ages.Values do
      WriteLn(V);
    Ages.Remove('Bob');
  finally
    Ages.Free;
  end;

  // TQueue<T> (FIFO) and TStack<T> (LIFO)
  Queue := TQueue<string>.Create;
  try
    Queue.Enqueue('first');
    Queue.Enqueue('second');
    WriteLn(Queue.Dequeue);       // 'first'
  finally
    Queue.Free;
  end;

  // TObjectList<T> (owns its objects — frees them on Clear/Free)
  // uses System.Generics.Collections;
  // var People: TObjectList<TPerson>;
  // People := TObjectList<TPerson>.Create;
  // People.Add(TPerson.Create);  // freed when People is freed
end;

Array-Algorithmen & Sortierung

TArray ist eine Utility-Klasse für Array-Operationen: Sort (mit optionalem benutzerdefiniertem IComparer), BinarySearch (schnelle Suche in sortierten Arrays), Reverse, Copy. TComparer<T>.Construct erstellt eine Vergleichsfunktion inline (anonyme Methode). Sortieren nach einem Feld erfordert einen benutzerdefinierten Vergleicher. BinarySearch gibt einen Boolean und den gefundenen Index zurück — das Array MUSS vorher sortiert sein. Für komplexe Suchen ist eine lineare Schleife mit Break einfach und klar. TArray.Sort ist ein Quicksort (O(n log n) Durchschnitt).

delphi
uses
  System.Generics.Collections, System.Generics.Defaults;

var
  Nums: TArray<Integer>;
  People: TArray<TPerson>;
  i: Integer;
begin
  // sort an array
  Nums := TArray<Integer>.Create(5, 3, 1, 4, 2);
  TArray.Sort<Integer>(Nums);
  // Nums = [1, 2, 3, 4, 5]

  // sort descending (custom comparer)
  TArray.Sort<Integer>(Nums, TComparer<Integer>.Construct(
    function(const L, R: Integer): Integer
    begin
      Result := R - L;   // reverse comparison
    end));

  // binary search (array must be sorted)
  TArray.Sort<Integer>(Nums);
  var Found: Boolean := TArray.BinarySearch<Integer>(Nums, 3, i);
  if Found then
    WriteLn('Found at index ', i);

  // sort array of records by a field
  type TPerson = record Name: string; Age: Integer; end;
  SetLength(People, 3);
  People[0] := TPerson.Create('Alice', 30);
  People[1] := TPerson.Create('Bob', 25);
  People[2] := TPerson.Create('Carol', 28);

  TArray.Sort<TPerson>(People, TComparer<TPerson>.Construct(
    function(const L, R: TPerson): Integer
    begin
      Result := L.Age - R.Age;   // sort by age
    end));

  for i := 0 to High(People) do
    WriteLn(People[i].Name, ': ', People[i].Age);

  // reverse an array
  TArray.Reverse<Integer>(Nums);

  // copy an array
  var Copy: TArray<Integer>;
  Copy := Copy(Nums, 0, Length(Nums));

  // find with a predicate
  var FoundIdx: Integer := -1;
  for i := 0 to High(People) do
    if People[i].Age > 28 then
    begin
      FoundIdx := i;
      Break;
    end;
end;
05

Prozeduren, Funktionen & Parameter

Prozeduren & Funktionen

Prozeduren (kein Rückgabewert) und Funktionen (geben einen Wert zurück) sind Delphis Subroutinen. Die Result-Variable ist der Rückgabewert — weisen Sie ihr zu (die Funktion kehrt zurück, wenn sie endet). Exit() kehrt sofort mit einem Wert zurück (moderne Syntax). Forward-Deklarationen lassen Sie eine Funktion aufrufen, bevor ihr Körper definiert ist (nützlich für gegenseitige Rekursion). Funktionen können jeden Typ zurückgeben, einschließlich Records, Arrays und Objekte. Exit ohne Wert verlässt einfach die Prozedur. Die Result-Variable ist implizit deklariert und entspricht dem Rückgabetyp.

delphi
// Procedure: performs an action (no return value)
procedure Greet(Name: string);
begin
  WriteLn('Hello, ', Name, '!');
end;

// Function: returns a value
function Add(A, B: Integer): Integer;
begin
  Result := A + B;          // Result is the return variable
  // Exit(42);              // alternative: return immediately with 42
end;

// Function with multiple return paths
function Classify(Score: Integer): string;
begin
  if Score >= 90 then
    Exit('A');              // return immediately
  if Score >= 80 then
    Exit('B');
  Result := 'F';            // default return
end;

// Function returning a record
function MakePoint(X, Y: Integer): TPoint;
begin
  Result.X := X;
  Result.Y := Y;
end;

// forward declaration (use before full definition)
function Calc(X: Integer): Integer; forward;

procedure Demo;
var
  Sum: Integer;
  P: TPoint;
begin
  Greet('Alice');               // procedure call
  Sum := Add(3, 4);             // function call
  WriteLn(Sum);                 // 7
  WriteLn(Classify(85));        // B
  P := MakePoint(3, 4);
  WriteLn(Calc(10));
end;

function Calc(X: Integer): Integer;
begin
  Result := X * 2;
end;

Parameter: Const, Var, Out, Default

const: Nur-Lese-Parameter (vermeidet auch das Kopieren von Strings/Arrays — effizient). var: Pass-by-Reference (modifiziert die Variable des Aufrufers — wie ref in C#). out: Nur-Ausgabe (Aufrufer initialisiert nicht; Funktion setzt sie). Default-Parameter müssen zuletzt kommen. Open-Array-Parameter (array of T) akzeptieren jedes Array oder ein Literal [1,2,3] — verwenden Sie const für Effizienz. const wird für Strings und Arrays bevorzugt (keine Kopie); verwenden Sie var nur, wenn Sie den Wert des Aufrufers modifizieren müssen. Open Arrays sind 0-indiziert unabhängig von den Grenzen des Quell-Arrays.

delphi
// const: can't be modified (also efficient for strings/arrays)
procedure Show(const S: string);
begin
  WriteLn(S);
  // S := 'new';  // ERROR: can't modify const
end;

// var: pass by reference (can modify caller's variable)
procedure Swap(var A, B: Integer);
var
  Temp: Integer;
begin
  Temp := A;
  A := B;
  B := Temp;
end;

// out: output-only parameter (caller doesn't need to initialize)
procedure GetValues(out X, Y: Integer);
begin
  X := 10;
  Y := 20;
end;

// default parameters (must be at the end)
function Power(Base: Double; Exp: Integer = 2): Double;
begin
  Result := Power(Base, Exp);   // Math.Power
end;

// open array parameter (accepts any array)
function Sum(const Values: array of Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to High(Values) do
    Result := Result + Values[i];
end;

// 'const' for open arrays (efficient — no copy)
procedure ShowAll(const Items: array of string);
var
  S: string;
begin
  for S in Items do
    WriteLn(S);
end;

var
  X, Y: Integer;
  Nums: array[0..4] of Integer;
begin
  Swap(X, Y);              // var: modifies X and Y
  GetValues(X, Y);         // out: sets X and Y
  WriteLn(Power(3));       // 9 (Exp defaults to 2)
  WriteLn(Power(2, 10));   // 1024
  WriteLn(Sum([1, 2, 3, 4, 5]));   // 15 (open array literal)
  ShowAll(['a', 'b', 'c']);
end;

Überladen & Standardparameter

Überladen lässt mehrere Routinen einen Namen mit verschiedenen Parameterlisten teilen — der Compiler wählt die beste Übereinstimmung. Die 'overload'-Direktive ist erforderlich. Überladen ist sauberer, als verschiedene Namen zu erfinden (AddInt, AddDouble). Standardparameter sind eine Alternative — Aufrufer können sie weglassen. Bevorzugen Sie Überladen, wenn die Logik sich nach Typ unterscheidet; verwenden Sie Defaults für optionale Werte. Mehrdeutigkeit (zwei Überladungen, die gleich gut passen) ist ein Kompilierfehler. Überladungen müssen sich in Parameteranzahl oder Typen unterscheiden (nur der Rückgabetyp reicht nicht).

delphi
// overloading: same name, different parameters
function Add(A, B: Integer): Integer; overload;
begin
  Result := A + B;
end;

function Add(A, B: Double): Double; overload;
begin
  Result := A + B;
end;

function Add(A, B, C: Integer): Integer; overload;
begin
  Result := A + B + C;
end;

function Add(const Values: array of Integer): Integer; overload;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to High(Values) do
    Result := Result + Values[i];
end;

// default parameters (alternative to some overloads)
function CreateRect(Left, Top: Integer; Width: Integer = 100;
  Height: Integer = 50): TRect;
begin
  Result := Rect(Left, Top, Left + Width, Top + Height);
end;

var
  R: TRect;
begin
  WriteLn(Add(1, 2));           // 3 (Integer overload)
  WriteLn(Add(1.5, 2.5));       // 4.0 (Double overload)
  WriteLn(Add(1, 2, 3));        // 6 (3-arg overload)
  WriteLn(Add([1, 2, 3, 4]));   // 10 (array overload)

  R := CreateRect(10, 20);              // uses defaults: 100x50
  R := CreateRect(10, 20, 200);         // Width=200, Height=50
  R := CreateRect(10, 20, 200, 100);    // all specified
end;

Anonyme Methoden & Closures

Anonyme Methoden (Closures) sind Inline-Funktionen/Prozeduren, die 'reference to'-Typen zugewiesen werden. Sie erfassen Variablen aus ihrem umschließenden Gültigkeitsbereich (Closures). 'reference to function'/'reference to procedure' sind die Delegate-Typen. Anonyme Methoden ermöglichen funktionale Programmierung: Higher-Order-Funktionen (Apply nimmt eine Funktion), Closures (MakeMultiplier gibt eine Funktion zurück, die sich an Factor erinnert) und benutzerdefinierte Vergleicher (TComparer<T>.Construct). Sie sind unerlässlich für Generics-Sortierung, Event-Handler und Callbacks. Die erfassten Variablen werden heap-zugewiesen (sie überleben die umschließende Funktion).

delphi
type
  TMathFunc = reference to function(X: Integer): Integer;
  TNotifyProc = reference to procedure(Msg: string);

// function that takes a function
function Apply(Func: TMathFunc; Values: array of Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to High(Values) do
    Result := Result + Func(Values[i]);
end;

// function that returns a function (closure)
function MakeMultiplier(Factor: Integer): TMathFunc;
begin
  Result := function(X: Integer): Integer
           begin
             Result := X * Factor;   // captures Factor
           end;
end;

var
  Double: TMathFunc;
  Triple: TMathFunc;
begin
  // anonymous method (inline function)
  Double := function(X: Integer): Integer
            begin
              Result := X * 2;
            end;

  WriteLn(Double(21));    // 42

  // use with higher-order functions
  WriteLn(Apply(Double, [1, 2, 3, 4]));   // 20 (2+4+6+8)

  // closure: captures the Factor variable
  Triple := MakeMultiplier(3);
  WriteLn(Triple(5));     // 15

  // anonymous procedure
  var Log: TNotifyProc := procedure(Msg: string)
    begin
      WriteLn('[LOG] ', Msg);
    end;
  Log('Hello');

  // use with TList.Sort (custom comparison)
  var Nums: TList<Integer>;
  Nums := TList<Integer>.Create;
  try
    Nums.AddRange([5, 3, 1, 4, 2]);
    Nums.Sort(TComparer<Integer>.Construct(
      function(const L, R: Integer): Integer
      begin
        Result := L - R;
      end));
  finally
    Nums.Free;
  end;
end;

Rekursion & Hilfsroutinen

Rekursion ist eine Funktion, die sich selbst aufruft — benötigt einen Basisfall zum Terminieren. Fakultät und Fibonacci sind klassische Beispiele. Endrekursion (wo der rekursive Aufruf die letzte Operation ist) kann vom Compiler optimiert werden. Verschachtelte Prozeduren/Funktionen werden innerhalb einer anderen Routine deklariert und können auf ihre Variablen zugreifen (lexikalische Bindung) — nützlich für Hilfsfunktionen, die nicht außerhalb sichtbar sein müssen. Achten Sie auf Stack-Überlauf bei tiefer Rekursion (verwenden Sie Iteration für große Eingaben). Memoization (Zwischenspeichern von Ergebnissen) kann rekursive Algorithmen wie Fibonacci beschleunigen.

delphi
// classic recursion
function Factorial(N: Integer): Integer;
begin
  if N <= 1 then
    Result := 1
  else
    Result := N * Factorial(N - 1);
end;

// tail recursion (compiler may optimize)
function SumRange(N: Integer; Acc: Integer = 0): Integer;
begin
  if N = 0 then
    Result := Acc
  else
    Result := SumRange(N - 1, Acc + N);
end;

// Fibonacci (naive — exponential time)
function Fib(N: Integer): Integer;
begin
  if N < 2 then
    Result := N
  else
    Result := Fib(N - 1) + Fib(N - 2);
end;

// nested procedure (helper with access to outer variables)
procedure ProcessData(Data: array of Integer);
var
  Total: Integer;

  procedure SumAll;     // nested, sees Total and Data
  var
    i: Integer;
  begin
    Total := 0;
    for i := 0 to High(Data) do
      Total := Total + Data[i];
  end;

  function Average: Double;   // nested function
  begin
    if Length(Data) = 0 then
      Result := 0
    else
      Result := Total / Length(Data);
  end;

begin
  SumAll;               // calls nested procedure
  WriteLn('Sum: ', Total);
  WriteLn('Avg: ', Average:0:2);
end;

begin
  WriteLn(Factorial(5));     // 120
  WriteLn(SumRange(10));     // 55
  WriteLn(Fib(10));          // 55
  ProcessData([1, 2, 3, 4, 5]);
end;
06

Klassen & OOP

Klassendefinition, Konstruktor & Destruktor

Klassen sind Referenztypen (heap-zugewiesen, über Zeiger zugegriffen). Create ist der Konstruktor; Destroy ist der Destruktor (immer override; von Free aufgerufen). 'inherited' ruft die Methode der Basisklasse auf. Felder verwenden konventionsgemäß das F-Präfix. Eigenschaften (property X: Type read GetX write SetX) bieten kontrollierten Zugriff — Aufrufer verwenden P.Age, aber der Setter validiert. Sichtbarkeit: private (nur Unit in älterem Delphi; strict private ist wirklich privat), protected (Unterklassen), public (jeder), published (RTTI, für Formulare/Inspektoren). Umschließen Sie Objekterstellung immer mit try..finally, um sicherzustellen, dass Free aufgerufen wird.

delphi
type
  TPerson = class
  private
    FName: string;          // private field (convention: F prefix)
    FAge: Integer;
    procedure SetAge(Value: Integer);   // setter for validation
  protected
    // visible to subclasses
    function GetDescription: string; virtual;
  public
    constructor Create(Name: string; Age: Integer);   // constructor
    destructor Destroy; override;                      // destructor
    // properties (with getters/setters)
    property Name: string read FName;                  // read-only
    property Age: Integer read FAge write SetAge;      // validated
    property Description: string read GetDescription;
    // method
    function Greet: string; virtual;
  end;

constructor TPerson.Create(Name: string; Age: Integer);
begin
  inherited Create;         // call base constructor (TObject.Create)
  FName := Name;
  FAge := Age;
end;

destructor TPerson.Destroy;
begin
  // free owned objects here
  inherited;                // call base destructor
end;

procedure TPerson.SetAge(Value: Integer);
begin
  if (Value < 0) or (Value > 150) then
    raise ERangeError.Create('Invalid age');
  FAge := Value;
end;

function TPerson.GetDescription: string;
begin
  Result := Format('%s (%d)', [FName, FAge]);
end;

function TPerson.Greet: string;
begin
  Result := 'Hi, I am ' + FName;
end;

var
  P: TPerson;
begin
  P := TPerson.Create('Alice', 30);
  try
    WriteLn(P.Greet);          // Hi, I am Alice
    WriteLn(P.Description);    // Alice (30)
    P.Age := 31;               // uses setter
    // P.Age := 200;           // raises ERangeError
  finally
    P.Free;                    // calls destructor
  end;
end;

Eigenschaften & Indizierte Eigenschaften

Eigenschaften kapseln Feldzugriff mit Gettern/Settern. Nur-Lese-Eigenschaften haben nur einen 'read'-Spezifizierer. Die 'default'-Direktive macht eine indizierte Eigenschaft zur Standard-Eigenschaft — sodass L[i] anstelle von L.Items[i] funktioniert. Eigenschaften können direkten Feldzugriff haben (read FCount) oder Methodenzugriff (read GetItem write SetItem) für Validierung/Berechnung. Indizierte Eigenschaften ermöglichen array-ähnliche Syntax. Published-Eigenschaften (published-Abschnitt) sind für RTTI und den Formular-Designer sichtbar. Eigenschaften sind Delphis Weg, Daten sicher offenzulegen — bevorzugen Sie sie immer gegenüber öffentlichen Feldern.

delphi
type
  TList = class
  private
    FItems: array of Integer;
    FCount: Integer;
    function GetItem(Index: Integer): Integer;
    procedure SetItem(Index: Integer; Value: Integer);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Add(Value: Integer);
    // default array property (enables List[i] syntax)
    property Items[Index: Integer]: Integer read GetItem write SetItem; default;
    property Count: Integer read FCount;
  end;

constructor TList.Create;
begin
  inherited Create;
  FCount := 0;
end;

destructor TList.Destroy;
begin
  SetLength(FItems, 0);
  inherited;
end;

function TList.GetItem(Index: Integer): Integer;
begin
  if (Index < 0) or (Index >= FCount) then
    raise ERangeError.Create('Index out of range');
  Result := FItems[Index];
end;

procedure TList.SetItem(Index: Integer; Value: Integer);
begin
  if (Index < 0) or (Index >= FCount) then
    raise ERangeError.Create('Index out of range');
  FItems[Index] := Value;
end;

procedure TList.Add(Value: Integer);
begin
  Inc(FCount);
  SetLength(FItems, FCount);
  FItems[FCount - 1] := Value;
end;

var
  L: TList;
begin
  L := TList.Create;
  try
    L.Add(10);
    L.Add(20);
    WriteLn(L[0]);    // 10 (default property — no need for L.Items[0])
    L[1] := 99;       // uses setter
    WriteLn(L.Count); // 2
  finally
    L.Free;
  end;
end;

Vererbung & Polymorphismus

Vererbung: TDog = class(TAnimal) bedeutet, dass TDog von TAnimal erbt. 'virtual' markiert eine Methode für Polymorphismus; 'override' ersetzt sie in einer Unterklasse. Zur Laufzeit läuft die Methode des AKTUELLEN Objekts (virtueller Dispatch) — Speak auf einer TAnimal-Referenz, die ein TDog hält, ruft TDog.Speak auf. Statische Methoden (Move) werden durch den Typ der Variablen bestimmt, nicht des Objekts. 'inherited' ruft die Basismethode auf. Konstruktoren können virtuell sein (Factory-Muster). Verwenden Sie virtual/override für Polymorphismus; statische Methoden, wenn das Verhalten feststeht. Geben Sie erstellte Objekte immer frei.

delphi
type
  TAnimal = class
  public
    constructor Create; virtual;       // virtual constructor (factory pattern)
    function Speak: string; virtual;   // virtual: can be overridden
    function Move: string;             // static: can't be overridden
  end;

  TDog = class(TAnimal)
  public
    constructor Create; override;
    function Speak: string; override;  // override the virtual method
  end;

  TCat = class(TAnimal)
  public
    function Speak: string; override;
  end;

constructor TAnimal.Create;
begin
  inherited;
end;

function TAnimal.Speak: string;
begin
  Result := '...';
end;

function TAnimal.Move: string;
begin
  Result := 'Moving';
end;

constructor TDog.Create;
begin
  inherited Create;    // call TAnimal.Create
  WriteLn('Dog created');
end;

function TDog.Speak: string;
begin
  Result := 'Woof';
end;

function TCat.Speak: string;
begin
  Result := 'Meow';
end;

// polymorphism: array of base class, different behaviors
var
  Animals: array of TAnimal;
  i: Integer;
begin
  SetLength(Animals, 3);
  Animals[0] := TDog.Create;
  Animals[1] := TCat.Create;
  Animals[2] := TAnimal.Create;

  for i := 0 to High(Animals) do
  begin
    WriteLn(Animals[i].Speak);   // Woof, Meow, ... (virtual dispatch)
    WriteLn(Animals[i].Move);    // Moving, Moving, Moving (static)
  end;

  for i := 0 to High(Animals) do
    Animals[i].Free;
end;

Abstrakte Methoden & Klassenmethoden

Abstrakte Klassen (class abstract) können nicht instanziiert werden — sie definieren einen Vertrag für Unterklassen. Abstrakte Methoden (virtual; abstract) haben keine Implementierung — Unterklassen MÜSSEN sie überschreiben. Dies erzwingt, dass jede Form Area/Perimeter bereitstellt. Klassenmethoden (class function/procedure) benötigen keine Instanz — rufen Sie über TShape.ShapeCount auf. Klassenvariablen (class var) werden über alle Instanzen geteilt. Das Template-Method-Muster: TShape.Describe ruft die abstrakten Area/Perimeter auf, die von Unterklassen ausgefüllt werden. Abstrakte Methoden definieren 'was'; Unterklassen definieren 'wie'.

delphi
type
  TShape = class abstract        // can't be instantiated directly
  public
    function Area: Double; virtual; abstract;   // must be overridden
    function Perimeter: Double; virtual; abstract;
    procedure Describe; virtual;
    // class method (no instance needed)
    class function ShapeCount: Integer; static;
    class var FCount: Integer;    // class variable (shared)
  end;

  TCircle = class(TShape)
  private
    FRadius: Double;
  public
    constructor Create(Radius: Double);
    function Area: Double; override;
    function Perimeter: Double; override;
  end;

  TRectangle = class(TShape)
  private
    FWidth, FHeight: Double;
  public
    constructor Create(W, H: Double);
    function Area: Double; override;
    function Perimeter: Double; override;
  end;

class function TShape.ShapeCount: Integer;
begin
  Result := FCount;
end;

procedure TShape.Describe;
begin
  WriteLn(Format('Area: %.2f, Perimeter: %.2f', [Area, Perimeter]));
end;

constructor TCircle.Create(Radius: Double);
begin
  inherited Create;
  FRadius := Radius;
  Inc(FCount);
end;

function TCircle.Area: Double;
begin
  Result := Pi * FRadius * FRadius;
end;

function TCircle.Perimeter: Double;
begin
  Result := 2 * Pi * FRadius;
end;

var
  S: TShape;
begin
  // TShape.Create;  // ERROR: abstract class can't be instantiated
  S := TCircle.Create(5);
  try
    S.Describe;          // Area: 78.54, Perimeter: 31.42
    WriteLn(TShape.ShapeCount);  // class method (no instance)
  finally
    S.Free;
  end;
end;

Schnittstellen & Mehrfachvererbung

Schnittstellen sind reine Verträge (keine Felder, keine Implementierung) — Delphis Weg, Mehrfachvererbung von Typen zu erreichen. Eine Klasse kann viele Schnittstellen implementieren (TButton implementiert IComparable, IDrawable, IDisposable). Schnittstellen können GUIDs für QueryInterface/as-Casts haben. TInterfacedObject bietet Referenzzählung — wenn die letzte Schnittstellenreferenz den Gültigkeitsbereich verlässt, wird das Objekt automatisch freigegeben (rufen Sie nicht Free auf!). Verwenden Sie Schnittstellen zur Entkopplung: Code hängt von IDrawable ab, nicht von TButton. Der 'as'-Operator castet auf eine Schnittstelle (wirft, wenn nicht unterstützt). Schnittstellen sind das Rückgrat von Delphis COM-Unterstützung und modernen Plugin-Architekturen.

delphi
type
  // interface: pure contract (no implementation, no fields)
  IComparable = interface
    function CompareTo(Other: TObject): Integer;
  end;

  IDrawable = interface
    procedure Draw;
  end;

  // interfaces have GUIDs (for QueryInterface / as operator)
  IDisposable = interface
    ['{12345678-1234-1234-1234-123456789012}']
    procedure Dispose;
  end;

  // class implementing multiple interfaces
  TButton = class(TInterfacedObject, IComparable, IDrawable, IDisposable)
  private
    FLabel: string;
  public
    constructor Create(ALabel: string);
    function CompareTo(Other: TObject): Integer;
    procedure Draw;
    procedure Dispose;
  end;

constructor TButton.Create(ALabel: string);
begin
  FLabel := ALabel;
end;

function TButton.CompareTo(Other: TObject): Integer;
begin
  Result := CompareText(FLabel, (Other as TButton).FLabel);
end;

procedure TButton.Draw;
begin
  WriteLn('Drawing button: ', FLabel);
end;

procedure TButton.Dispose;
begin
  WriteLn('Disposing ', FLabel);
end;

var
  Btn: TButton;
  Drawable: IDrawable;
  Comp: IComparable;
begin
  Btn := TButton.Create('OK');
  Btn.Draw;

  // assign to interface variable (reference counting!)
  Drawable := Btn as IDrawable;
  Drawable.Draw;

  Comp := Btn;
  WriteLn(Comp.CompareTo(Btn));   // 0

  // TInterfacedObject uses reference counting
  // when the last interface reference is released, the object is freed
  // (don't call Free on interface-referenced objects!)
end;
07

Ausnahmen & Fehlerbehandlung

Try...Except...Finally

try...except fängt Ausnahmen ab (wie try/catch in C#). Jedes 'on E: ExceptionType do' behandelt eine spezifische Ausnahme. try...finally stellt sicher, dass Bereinigung unabhängig von Ausnahmen läuft (keine Ausnahmebehandlung — verwenden Sie es für Free-Aufrufe). Das Muster ist try...try...except...finally (inneres except für Behandlung, äußeres finally für Bereinigung). 'raise' (allein) löst die aktuelle Ausnahme erneut aus. Exception ist die Basisklasse; EFileNotFoundException, EInOutError sind Unterklassen. Geben Sie immer die spezifischste Ausnahme zuerst und Exception (Basis) zuletzt an. Lassen Sie niemals ein leeres except (schluckt stillschweigend Fehler).

delphi
var
  F: TextFile;
  S: string;
begin
  // try...except: catch exceptions
  try
    AssignFile(F, 'nonexistent.txt');
    Reset(F);
    ReadLn(F, S);
    CloseFile(F);
  except
    on E: EFileNotFoundException do
      WriteLn('File not found: ', E.Message);
    on E: EInOutError do
      WriteLn('I/O error: ', E.Message);
    on E: Exception do   // catch-all (must be last)
      WriteLn('Unexpected: ', E.ClassName, ': ', E.Message);
  end;

  // try...finally: cleanup (always runs, even on exception)
  var SL: TStringList;
  SL := TStringList.Create;
  try
    SL.LoadFromFile('data.txt');
    WriteLn(SL.Text);
  finally
    SL.Free;   // ALWAYS runs, even if an exception occurred
  end;

  // combined: try...try...except...finally
  SL := TStringList.Create;
  try
    try
      SL.LoadFromFile('data.txt');
    except
      on E: Exception do
      begin
        WriteLn('Error loading: ', E.Message);
        SL.Clear;   // fallback
      end;
    end;
    WriteLn(SL.Text);
  finally
    SL.Free;
  end;

  // re-raise
  try
    RiskyOperation;
  except
    on E: Exception do
    begin
      WriteLn('Logging: ', E.Message);
      raise;   // re-raise the same exception
    end;
  end;
end;

Auslösen & Benutzerdefinierte Ausnahmen

Raise erstellt eine Ausnahme: raise ExceptionType.Create('message'). CreateFmt ist wie Format + Create. Benutzerdefinierte Ausnahmen erben von Exception (oder einer spezifischen Unterklasse) und können zusätzliche Daten tragen (TransactionId). Beim Umschließen bewahren Sie das Original über SetInner oder einen Konstruktorparameter. Benutzerdefinierte Ausnahmen lassen Aufrufer spezifische Fehlertypen abfangen: fangen Sie ETransactionError separat von ERangeError ab. Schließen Sie immer eine aussagekräftige Nachricht ein. Häufige eingebaute: ERangeError, EDivByZero, EConvertError, EFileNotFoundException, EAccessViolation, EListError.

delphi
uses
  System.SysUtils;

// raise built-in exceptions
procedure CheckAge(Age: Integer);
begin
  if Age < 0 then
    raise ERangeError.CreateFmt('Age cannot be negative: %d', [Age]);
  if Age > 150 then
    raise ERangeError.Create('Age unrealistic');
end;

// raise with inner exception (wrapping)
function LoadConfig(Path: string): string;
begin
  try
    Result := TFile.ReadAllText(Path);
  except
    on E: Exception do
      raise EConfigError.Create('Config load failed').SetInner(E);
  end;
end;

// custom exception class
type
  ETransactionError = class(Exception)
  private
    FTransactionId: string;
  public
    constructor Create(const Msg, TxnId: string);
    property TransactionId: string read FTransactionId;
  end;

constructor ETransactionError.Create(const Msg, TxnId: string);
begin
  inherited Create(Msg);
  FTransactionId := TxnId;
end;

// using the custom exception
procedure ProcessPayment(Amount: Double; TxnId: string);
begin
  if Amount <= 0 then
    raise ETransactionError.Create('Amount must be positive', TxnId);
  // ... process
end;

var
  E: Exception;
begin
  try
    CheckAge(-5);
  except
    on E: ERangeError do
      WriteLn('Range error: ', E.Message);
  end;

  try
    ProcessPayment(-100, 'TXN-001');
  except
    on E: ETransactionError do
      WriteLn('Transaction ', E.TransactionId, ' failed: ', E.Message);
  end;
end;

Assertionen & Debugging

Assert prüft eine Bedingung und löst EAssertionFailed aus, wenn false — verwenden Sie für Invarianten (Bedingungen, die immer wahr sein müssen). Assertionen werden mit {$C-} deaktiviert (oder in Release-Builds entfernt) — verwenden Sie sie nicht für Eingabevalidierung (verwenden Sie Ausnahmen). OutputDebugString protokolliert im Ereignisprotokoll der IDE (keine Datei-E/A). TStopwatch misst verstrichene Zeit präzise. Exception.StackTrace benötigt Debug-Info (.map-Datei oder JCLDebug/FastMM). {$IFDEF DEBUG} aktiviert nur-Debug-Code. Verwenden Sie Assertionen für interne Logikfehler und Ausnahmen für Benutzer-/externe Fehler.

delphi
uses
  System.SysUtils, System.Diagnostics;

var
  Age: Integer;
  SW: TStopwatch;
begin
  // Assert: checks a condition (only in {$C+} / debug builds)
  Age := 30;
  Assert(Age >= 0, 'Age should be non-negative');
  // Assert(Age < 0, 'This will raise EAssertionFailed');

  // {$C+} / {$C-}: enable/disable assertions
  {$C-}   // disable assertions (release builds)
  Assert(False, 'This won''t fire');
  {$C+}   // re-enable

  // OutputDebugString (visible in IDE debugger)
  OutputDebugString('Processing started');

  // TStopwatch for timing
  SW := TStopwatch.StartNew;
  Sleep(100);
  SW.Stop;
  WriteLn(Format('Elapsed: %d ms', [SW.ElapsedMilliseconds]));

  // raise with stack trace (uses System.DebugUtils / JCLDebug)
  try
    raise Exception.Create('Test error');
  except
    on E: Exception do
    begin
      WriteLn(E.Message);
      WriteLn(E.StackTrace);   // needs debug info / map file
    end;
  end;

  // conditional compilation
  {$IFDEF DEBUG}
  WriteLn('Debug build');
  {$ELSE}
  WriteLn('Release build');
  {$ENDIF}

  // Trace (simple logging)
  {$IFDEF DEBUG}
  WriteLn('[TRACE] Entering ProcessData');
  {$ENDIF}
end;

Ausnahmebehandlungsmuster

Häufige Ausnahmemuster: (1) Wiederholungsschleifen — umschließen Sie eine fehleranfällige Operation in einem try/except innerhalb einer While-Schleife, die nach MaxRetries erneut wirft. (2) Fallback-Werte — fangen Sie eine spezifische Ausnahme (EConvertError) ab und geben Sie einen Standardwert zurück; schlucken Sie nur Ausnahmen, die Sie wirklich erwarten. (3) Ressourcenschutz — umschließen Sie Create/Free immer in try/finally, sodass Objekte auch bei Ausnahmen freigegeben werden (dies ist das wichtigste Delphi-Idiom). (4) Mehrere Ressourcen — verschachteln Sie try/finally-Blöcke; erwerben Sie jede Ressource innerhalb ihres eigenen geschützten Blocks. (5) Validierung — werfen Sie spezifische Ausnahmetypen (EArgumentException, ERangeError) früh mit beschreibenden Nachrichten. Fangen Sie niemals Exception ab und fahren Sie stillschweigend fort — protokollieren Sie mindestens. Bevorzugen Sie try/finally für Bereinigung und try/except für echte Wiederherstellung.

delphi
uses
  System.SysUtils, System.Classes;

// Pattern 1: Retry with backoff
function DownloadWithRetry(const URL: string; MaxRetries: Integer): string;
var
  Attempt: Integer;
  Done: Boolean;
begin
  Attempt := 0;
  Done := False;
  while (not Done) and (Attempt < MaxRetries) do
  begin
    Inc(Attempt);
    try
      Result := DoDownload(URL);   // may raise EDownloadError
      Done := True;
    except
      on E: Exception do
      begin
        if Attempt >= MaxRetries then
          raise;   // re-raise after final attempt
        Sleep(Attempt * 500);   // exponential-ish backoff
      end;
    end;
  end;
end;

// Pattern 2: Fallback / default value
function SafeReadInt(const SL: TStringList; const Key: string; Default: Integer): Integer;
begin
  try
    Result := StrToInt(SL.Values[Key]);
  except
    on EConvertError do
      Result := Default;   // swallow and use default
  end;
end;

// Pattern 3: Resource protection (always Free)
procedure ProcessFile(const Path: string);
var
  SL: TStringList;
begin
  SL := TStringList.Create;
  try
    SL.LoadFromFile(Path);
    Transform(SL);
    SL.SaveToFile(Path + '.bak');
  finally
    SL.Free;   // guaranteed cleanup
  end;
end;

// Pattern 4: Acquire multiple resources safely
procedure CopyFile(const Src, Dst: string);
var
  SrcList, DstList: TStringList;
begin
  SrcList := TStringList.Create;
  try
    SrcList.LoadFromFile(Src);
    DstList := TStringList.Create;
    try
      DstList.Assign(SrcList);
      DstList.SaveToFile(Dst);
    finally
      DstList.Free;
    end;
  finally
    SrcList.Free;
  end;
end;

// Pattern 5: Validation with multiple checks
procedure ValidateUser(const Name: string; Age: Integer);
begin
  if Name = '' then
    raise EArgumentException.Create('Name required');
  if Length(Name) > 50 then
    raise EArgumentException.Create('Name too long');
  if (Age < 0) or (Age > 150) then
    raise ERangeError.CreateFmt('Invalid age: %d', [Age]);
end;

Protokollierung & Fehlerberichterstattung

Ein Produktions-Logger benötigt: (1) Thread-Sicherheit — TCriticalSection serialisiert Schreibvorgänge (mehrere Threads können gleichzeitig protokollieren). (2) Schweregrade — TLogLevel-Enum ermöglicht Filtern (z. B. llDebug in Produktion unterdrücken). (3) Formatierte Ausgabe — DateTime + Level + Nachricht pro Zeile, später parsbar. (4) Flush nach jedem Schreiben — sodass Protokolle Abstürze überleben (ungeflushte gepufferte Schreibvorgänge gehen bei AV verloren). (5) Ausnahmeprotokollierung — LogException erfasst ClassName + Message + Kontext. Das Log-and-Reraise-Muster zeichnet den Fehler auf, lässt aber obere Schichten ihn trotzdem behandeln. Für hochleistungs-Protokollierung ziehen Sie lock-freie Warteschlangen oder externe Bibliotheken (wie Log4Delphi) in Betracht. Geben Sie den Logger immer in finally frei, um das Datei-Handle zu schließen.

delphi
uses
  System.SysUtils, System.Classes, System.IOUtils, System.SyncObjs;

type
  TLogLevel = (llDebug, llInfo, llWarning, llError, llFatal);

  TLogger = class
  private
    FLock: TCriticalSection;
    FFile: TextFile;
    FMinLevel: TLogLevel;
    function LevelToStr(L: TLogLevel): string;
  public
    constructor Create(const LogPath: string; MinLevel: TLogLevel);
    destructor Destroy; override;
    procedure Log(Level: TLogLevel; const Msg: string); overload;
    procedure Log(Level: TLogLevel; const Fmt: string; const Args: array of const); overload;
    procedure LogException(E: Exception; const Context: string);
  end;

constructor TLogger.Create(const LogPath: string; MinLevel: TLogLevel);
begin
  FLock := TCriticalSection.Create;
  FMinLevel := MinLevel;
  AssignFile(FFile, LogPath);
  if FileExists(LogPath) then
    Append(FFile)
  else
    Rewrite(FFile);
end;

destructor TLogger.Destroy;
begin
  CloseFile(FFile);
  FLock.Free;
  inherited;
end;

function TLogger.LevelToStr(L: TLogLevel): string;
begin
  case L of
    llDebug:   Result := 'DEBUG';
    llInfo:    Result := 'INFO';
    llWarning: Result := 'WARN';
    llError:   Result := 'ERROR';
    llFatal:   Result := 'FATAL';
  end;
end;

procedure TLogger.Log(Level: TLogLevel; const Msg: string);
begin
  if Level < FMinLevel then Exit;
  FLock.Enter;
  try
    WriteLn(FFile, Format('%s [%s] %s', [DateTimeToStr(Now), LevelToStr(Level), Msg]));
    Flush(FFile);   // ensure written to disk
  finally
    FLock.Leave;
  end;
end;

procedure TLogger.Log(Level: TLogLevel; const Fmt: string; const Args: array of const);
begin
  Log(Level, Format(Fmt, Args));
end;

procedure TLogger.LogException(E: Exception; const Context: string);
begin
  Log(llError, '%s: %s: %s', [Context, E.ClassName, E.Message]);
end;

// Usage
var
  Logger: TLogger;
begin
  Logger := TLogger.Create('app.log', llInfo);
  try
    Logger.Log(llInfo, 'Application started');
    try
      RiskyOperation;
    except
      on E: Exception do
      begin
        Logger.LogException(E, 'RiskyOperation');
        raise;   // log and re-raise
      end;
    end;
  finally
    Logger.Free;
  end;
end;
08

Datei-E/A & Streams

Textdateien (Legacy & Modern)

Zwei Ansätze: Legacy (AssignFile/Reset/Rewrite/ReadLn/WriteLn/CloseFile) ist klassisches Pascal — gut für einfache Text-E/A, aber fehleranfällig (standardmäßig keine Ausnahmen). Modern (TFile in System.IOUtils) ist sauberer: WriteAllText, ReadAllText, ReadAllLines, AppendAllText, Exists. TFile-Methoden werfen bei Fehlern Ausnahmen (verwenden Sie try...except). Für große Dateien verwenden Sie StreamReader/StreamWriter (zeilenweise, geringer Speicher). Schließen Sie Dateien immer (CloseFile für Legacy, oder try..finally). TFile wird für neuen Code bevorzugt — es ist sicherer und konsistenter.

delphi
uses
  System.SysUtils, System.Classes, System.IOUtils;

// LEGACY: AssignFile / ReadLn / WriteLn (Pascal-style)
var
  F: TextFile;
  Line: string;
begin
  // write
  AssignFile(F, 'output.txt');
  Rewrite(F);                // create/overwrite
  try
    WriteLn(F, 'Hello, File!');
    WriteLn(F, 'Second line');
  finally
    CloseFile(F);
  end;

  // append
  AssignFile(F, 'output.txt');
  Append(F);
  try
    WriteLn(F, 'Appended line');
  finally
    CloseFile(F);
  end;

  // read line by line
  AssignFile(F, 'output.txt');
  Reset(F);                  // open for reading
  try
    while not EOF(F) do
    begin
      ReadLn(F, Line);
      WriteLn(Line);
    end;
  finally
    CloseFile(F);
  end;
end;

// MODERN: TFile (System.IOUtils)
var
  Content: string;
  Lines: TArray<string>;
begin
  // write all text
  TFile.WriteAllText('output.txt', 'Hello, World!');

  // append
  TFile.AppendAllText('log.txt', 'New entry' + sLineBreak);

  // read all text
  Content := TFile.ReadAllText('output.txt');

  // read all lines
  Lines := TFile.ReadAllLines('data.csv');
  for Line in Lines do
    WriteLn(Line);

  // write all lines
  TFile.WriteAllLines('nums.txt', ['one', 'two', 'three']);

  // file exists?
  if TFile.Exists('data.txt') then
    WriteLn('Found');
end;

TStringList für Dateien & CSV

TStringList ist der einfachste Weg, Textdateien und einfache CSVs zu handhaben. LoadFromFile/SaveToFile lesen/schreiben die gesamte Datei (eine Zeile pro Element). CommaText teilt/verbindet kommagetrennte Werte; DelimitedText verwendet ein benutzerdefiniertes Delimiter. Values[] behandelt Schlüssel=Wert-Paare (wie eine einfache INI-Datei). Sorted=True sortiert automatisch; Find macht eine binäre Suche (schneller als IndexOf in sortierten Listen). Duplicates steuert das Verhalten beim Hinzufügen von Duplikaten (dupIgnore, dupAccept, dupError). Für komplexe CSV (zitierte Felder mit Kommas) verwenden Sie einen dedizierten CSV-Parser. TStringList ist 0-indiziert.

delphi
uses
  System.Classes;

var
  SL: TStringList;
  i: Integer;
begin
  SL := TStringList.Create;
  try
    // load a text file (one line per item)
    SL.LoadFromFile('data.txt');

    // iterate lines
    for i := 0 to SL.Count - 1 do
      WriteLn(SL[i]);

    // add and save
    SL.Add('New line');
    SL.SaveToFile('output.txt');

    // CSV handling (CommaText)
    SL.Clear;
    SL.CommaText := 'Alice,30,NYC';
    WriteLn(SL[0]);   // Alice
    WriteLn(SL[1]);   // 30
    WriteLn(SL[2]);   // NYC

    // custom delimiter
    SL.Clear;
    SL.Delimiter := '|';
    SL.DelimitedText := 'a|b|c';

    // key=value pairs (INI-style)
    SL.Clear;
    SL.Values['name'] := 'Alice';
    SL.Values['age'] := '30';
    WriteLn(SL.Values['name']);   // Alice
    SL.SaveToFile('config.ini');

    // sorted list (auto-sorts on Add)
    SL.Clear;
    SL.Sorted := True;
    SL.Add('cherry');
    SL.Add('apple');
    SL.Add('banana');
    // SL is now: apple, banana, cherry

    // find (binary search — list must be sorted)
    if SL.Find('banana', i) then
      WriteLn('Found at ', i);

    // duplicate handling
    SL.Duplicates := dupIgnore;   // ignore duplicates (sorted only)
    SL.Duplicates := dupError;    // raise on duplicates
  finally
    SL.Free;
  end;
end;

Streams & Binär-E/A

TFileStream ist niederschwellige Byte-E/A (Read/Write-Puffer, Position für Seeking). TBinaryWriter/Reader schreiben/lesen typisierte Werte (Int32, Double, String, Boolean) — Lesereihenfolge muss Schreibreihenfolge entsprechen. TStreamReader/Writer verarbeiten Text mit Encoding (UTF-8, ASCII, Unicode) — verwenden Sie sie für Textdateien mit nicht-ASCII-Zeichen. Alle Streams müssen freigegeben werden (try..finally). fmCreate erstellt/überschreibt; fmOpenRead öffnet nur-Lese; fmOpenWrite öffnet zum Schreiben. Für große Dateien lesen Sie zeilenweise mit StreamReader (geringer Speicher) anstelle von LoadFromFile (lädt gesamte Datei).

delphi
uses
  System.Classes, System.SysUtils;

var
  FS: TFileStream;
  BR: TBinaryReader;
  BW: TBinaryWriter;
  SR: TStreamReader;
  SW: TStreamWriter;
  Buffer: TBytes;
  i: Integer;
begin
  // TFileStream: low-level file access
  FS := TFileStream.Create('data.bin', fmCreate);   // fmCreate, fmOpenRead, fmOpenWrite
  try
    // write bytes
    SetLength(Buffer, 4);
    Buffer[0] := 1; Buffer[1] := 2; Buffer[2] := 3; Buffer[3] := 4;
    FS.Write(Buffer[0], Length(Buffer));

    // read
    FS.Position := 0;   // rewind
    SetLength(Buffer, 4);
    FS.Read(Buffer[0], 4);
  finally
    FS.Free;
  end;

  // TBinaryWriter / TBinaryReader (typed binary I/O)
  BW := TBinaryWriter.Create('data.bin');
  try
    BW.Write(42);          // Integer
    BW.Write(3.14);        // Double
    BW.Write('Hello');     // length-prefixed string
    BW.Write(True);        // Boolean
  finally
    BW.Free;
  end;

  BR := TBinaryReader.Create('data.bin');
  try
    WriteLn(BR.ReadInt32);    // 42
    WriteLn(BR.ReadDouble);   // 3.14
    WriteLn(BR.ReadString);   // Hello
    WriteLn(BR.ReadBoolean);  // TRUE
  finally
    BR.Free;
  end;

  // TStreamReader / TStreamWriter (text, with encoding)
  SW := TStreamWriter.Create('utf8.txt', False, TEncoding.UTF8);
  try
    SW.WriteLine('Hello, UTF-8!');
    SW.WriteLine('héllo wörld');
  finally
    SW.Free;
  end;

  SR := TStreamReader.Create('utf8.txt', TEncoding.UTF8);
  try
    while not SR.EndOfStream do
      WriteLn(SR.ReadLine);
  finally
    SR.Free;
  end;
end;

Verzeichnis- & Pfadoperationen

System.IOUtils stellt TPath, TFile, TDirectory für moderne Dateioperationen bereit. TPath.Combine verbindet Pfade sicher (plattformübergreifend). TPath.GetTempFileName erstellt eine eindeutige Temp-Datei. TDirectory.GetFiles unterstützt Suchmuster und rekursive Suche (soAllDirectories). TFile.Copy/Move/Delete sind einfache Dateioperationen. TFileInfo gibt Dateimetadaten (Größe, Zeitstempel). Verwenden Sie immer TPath-Methoden anstelle von Zeichenkettenverkettung für Pfade (behandelt Trennzeichen korrekt). Diese Klassen funktionieren auf Windows, macOS und Linux (FireMonkey/FMX).

delphi
uses
  System.IOUtils, System.SysUtils;

var
  Files: TArray<string>;
  Dirs: TArray<string>;
  Path: string;
  i: Integer;
begin
  // TPath (cross-platform path handling)
  Path := TPath.Combine('folder', 'sub', 'file.txt');  // folder/sub/file.txt
  WriteLn(TPath.GetFileName('C:\temp\data.txt'));     // data.txt
  WriteLn(TPath.GetExtension('photo.JPG'));             // .JPG
  WriteLn(TPath.GetFileNameWithoutExtension('data.txt')); // data
  WriteLn(TPath.GetDirectoryName('C:\temp\data.txt'));  // C:\temp
  WriteLn(TPath.GetFullPath('data.txt'));               // absolute path

  // temp files
  WriteLn(TPath.GetTempFileName);   // creates a temp file
  WriteLn(TPath.GetTempPath);       // temp directory

  // special folders
  WriteLn(TPath.GetDocumentsPath);
  WriteLn(TPath.GetHomePath);

  // TDirectory
  TDirectory.CreateDirectory('backup\2024\june');

  Files := TDirectory.GetFiles('C:\temp', '*.txt');
  for i := 0 to High(Files) do
    WriteLn(Files[i]);

  // recursive search
  Files := TDirectory.GetFiles('C:\temp', '*.*', TSearchOption.soAllDirectories);

  Dirs := TDirectory.GetDirectories('C:\temp');
  for i := 0 to High(Dirs) do
    WriteLn(Dirs[i]);

  if TDirectory.Exists('old') then
    TDirectory.Delete('old', True);   // recursive delete

  // TFile operations
  TFile.Copy('source.txt', 'dest.txt', True);   // overwrite
  TFile.Move('old.txt', 'new.txt');
  TFile.Delete('unwanted.txt');

  // file info
  var Info: TFileInfo := TFileInfo.Create('data.txt');
  try
    WriteLn(Info.Length);          // size in bytes
    WriteLn(Info.CreationTime);
    WriteLn(Info.LastWriteTime);
    WriteLn(Info.Extension);
  finally
    Info.Free;
  end;
end;

INI-Dateien & JSON

TIniFile liest/schreibt INI-Konfigurationsdateien (Abschnitte in [Klammern], Schlüssel=Wert). ReadString/ReadInteger/ReadBool haben Standardwerte (werden zurückgegeben, wenn der Schlüssel fehlt). INI-Dateien sind einfache, menschenlesbare Konfigurationen — gut für Benutzereinstellungen. Für strukturierte Daten verwenden Sie JSON (System.JSON). TJSONObject baut/parst JSON-Objekte; TJSONArray für Arrays. AddPair fügt Schlüssel-Wert hinzu; GetValue<T> ruft typisierte Werte ab. ParseJSONValue parst einen JSON-String. JSON ist ideal für APIs, komplexe Konfigurationen und Datenaustausch. Für REST-Clients verwenden Sie TRESTClient oder Indy-Komponenten.

delphi
uses
  System.IniFiles, System.JSON, System.SysUtils;

// INI files (simple config)
var
  Ini: TIniFile;
begin
  Ini := TIniFile.Create('config.ini');
  try
    // write
    Ini.WriteString('User', 'Name', 'Alice');
    Ini.WriteInteger('User', 'Age', 30);
    Ini.WriteBool('User', 'Active', True);
    Ini.WriteDateTime('Session', 'LastLogin', Now);

    // read (with defaults)
    WriteLn(Ini.ReadString('User', 'Name', 'Unknown'));   // Alice
    WriteLn(Ini.ReadInteger('User', 'Age', 0));            // 30
    WriteLn(Ini.ReadBool('User', 'Active', False));        // TRUE

    // read a whole section
    var SL: TStringList;
    SL := TStringList.Create;
    try
      Ini.ReadSection('User', SL);
      // SL = ['Name', 'Age', 'Active']
    finally
      SL.Free;
    end;
  finally
    Ini.Free;
  end;
end;

// JSON (System.JSON)
var
  Obj: TJSONObject;
  Arr: TJSONArray;
  JSON: string;
  i: Integer;
begin
  // build JSON
  Obj := TJSONObject.Create;
  try
    Obj.AddPair('name', 'Alice');
    Obj.AddPair('age', TJSONNumber.Create(30));
    Obj.AddPair('active', TJSONBool.Create(True));

    var Hobbies := TJSONArray.Create;
    Hobbies.Add('reading').Add('coding');
    Obj.AddPair('hobbies', Hobbies);

    JSON := Obj.ToJSON;
    // {"name":"Alice","age":30,"active":true,"hobbies":["reading","coding"]}
  finally
    Obj.Free;
  end;

  // parse JSON
  Obj := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
  try
    WriteLn(Obj.GetValue<string>('name'));        // Alice
    WriteLn(Obj.GetValue<Integer>('age'));         // 30
    Arr := Obj.GetValue<TJSONArray>('hobbies');
    for i := 0 to Arr.Count - 1 do
      WriteLn(Arr.Items[i].Value);                 // reading, coding
  finally
    Obj.Free;
  end;
end;
09

VCL-Komponenten Vertieft

Formular- & Komponenten-Lebenszyklus

VCL-Formulare folgen einem strengen Lebenszyklus: OnCreate (Ressourcen zuweisen, initialisieren) → OnShow (Formular wird sichtbar) → OnActivate → OnResize → OnPaint → ... → OnCloseQuery (kann Schließen abbrechen) → OnClose → OnDestroy (Ressourcen freigeben). Paaren Sie OnCreate immer mit OnDestroy für Ressourcenverwaltung. OnCloseQuery lässt Sie das Schließen verhindern (setzen Sie CanClose := False). Sender ist die Komponente, die das Ereignis ausgelöst hat. Komponenten besitzen ihre Kinder — die Freigabe eines Formulars gibt automatisch alle seine Komponenten frei.

delphi
type
  TMainForm = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    FData: TStringList;
  public
    property Data: TStringList read FData;
  end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FData := TStringList.Create;     // allocate in OnCreate
  Caption := 'My App v1.0';
end;

procedure TMainForm.FormShow(Sender: TObject);
begin
  Edit1.SetFocus;                  // focus when form is visible
end;

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := MessageDlg('Close?', mtConfirmation, [mbYes, mbNo], 0) = mrYes;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FData.Free;                      // free in OnDestroy (pairs with OnCreate)
end;

Häufige VCL-Steuerelemente

VCL bietet eine reiche Auswahl an Steuerelementen: TEdit (einzeiliger Text), TMemo (mehrzeiliger Text), TLabel (nicht editierbarer Text), TButton, TCheckBox, TRadioButton, TComboBox (Dropdown), TListBox (auswählbare Liste). TStrings ist die Basis-Collection (Lines, Items sind TStrings). ItemIndex wählt Elemente aus (0-basiert, -1 = keins). ComboBox-Stile: csDropDown (editierbar), csDropDownList (nur-Lese). RadioGroup gruppiert Radio-Buttons mit einem ItemIndex. Sorted sortiert Elemente automatisch. PasswordChar maskiert Eingaben in TEdit.

delphi
// Edit, Memo, Label, Button, CheckBox, RadioButton
Edit1.Text := 'Hello';
Edit1.MaxLength := 50;
Edit1.PasswordChar := '*';         // mask input

Memo1.Lines.Add('Line 1');         // TStrings collection
Memo1.Lines.LoadFromFile('notes.txt');
Memo1.WordWrap := True;
Memo1.ScrollBars := ssVertical;

// ComboBox & ListBox
ComboBox1.Items.Add('Option A');
ComboBox1.ItemIndex := 0;          // select first
ComboBox1.Style := csDropDownList; // read-only selection

ListBox1.Items.Add('Item 1');
ListBox1.Sorted := True;
ShowMessage(ListBox1.Items[ListBox1.ItemIndex]);

// CheckBox & RadioButton
if CheckBox1.Checked then
  ShowMessage('Checked');
RadioGroup1.Items.Add('Red');
RadioGroup1.Items.Add('Green');
RadioGroup1.ItemIndex := 0;

StringGrid & DBGrid

TStringGrid zeigt tabellarische Daten in einem tabellenkalkulationsähnlichen Raster an. Cells[Col, Row] greift auf einzelne Zellen zu (0-indiziert). FixedRows/FixedCols erstellen nicht-scrollende Kopfzeilen. ColWidths/RowHeights passen Größen an. Optionen wie goEditing (editierbare Zellen), goColSizing (Spaltenbreite ändern), goRowSelect aktivieren Verhalten. OnDrawCell erlaubt benutzerdefiniertes Rendering mit dem Canvas. TDBGrid verbindet sich direkt mit einer DataSet (TTable, TQuery) über eine TDataSource — sie zeigt Datenbankdatensätze automatisch an und editiert sie. Verwenden Sie TDBGrid für Datenbankdaten, TStringGrid für In-Memory-Daten.

delphi
// TStringGrid - spreadsheet-like grid
StringGrid1.RowCount := 5;
StringGrid1.ColCount := 4;
StringGrid1.FixedRows := 1;        // header row
StringGrid1.FixedCols := 0;

// set headers
StringGrid1.Cells[0, 0] := 'Name';
StringGrid1.Cells[1, 0] := 'Age';
StringGrid1.Cells[2, 0] := 'City';

// populate data
StringGrid1.Cells[0, 1] := 'Alice';
StringGrid1.Cells[1, 1] := '30';
StringGrid1.Cells[2, 1] := 'NYC';

// customize appearance
StringGrid1.ColWidths[0] := 120;
StringGrid1.RowHeights[0] := 30;
StringGrid1.Options := StringGrid1.Options + [goEditing, goColSizing];

// onDrawCell for custom rendering
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
begin
  if ARow = 0 then
    StringGrid1.Canvas.Font.Style := [fsBold];
  StringGrid1.Canvas.TextRect(Rect, Rect.Left + 4, Rect.Top + 2,
    StringGrid1.Cells[ACol, ARow]);
end;

TTreeView & TListView

TTreeView zeigt hierarchische (Baum-)Daten mit TTreeNode-Objekten an. AddChild erstellt verschachtelte Knoten. Expand(True) klappt rekursiv auf. GetNext traversiert tiefenorientiert; GetNextSibling traversiert ebenenweise. BeginUpdate/EndUpdate bündeln Änderungen für Leistung. TListView zeigt Elemente in verschiedenen Ansichtsstilen an: vsIcon, vsSmallIcon, vsList, vsReport (Spalten). Caption ist die erste Spalte; SubItems hält nachfolgende Spalten. Beide unterstützen Owner-Data (virtuellen) Modus für große Datensätze über OnGetNodeData/OnData-Ereignisse.

delphi
// TTreeView - hierarchical data
var
  RootNode, ChildNode: TTreeNode;
begin
  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Clear;
    RootNode := TreeView1.Items.Add(nil, 'Root');
    ChildNode := TreeView1.Items.AddChild(RootNode, 'Child 1');
    TreeView1.Items.AddChild(RootNode, 'Child 2');
    TreeView1.Items.AddChild(ChildNode, 'Grandchild');
    RootNode.Expand(True);         // expand all children
  finally
    TreeView1.Items.EndUpdate;
  end;

  // iterate
  var Node := TreeView1.Items.GetFirstNode;
  while Node <> nil do
  begin
    ShowMessage(Node.Text);
    Node := Node.GetNext;          // depth-first traversal
  end;
end;

// TListView - report view with columns
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add.Caption := 'Name';
ListView1.Columns.Add.Caption := 'Size';
var Item := ListView1.Items.Add;
Item.Caption := 'file.txt';
Item.SubItems.Add('1.2 KB');

Dialoge & Häufige Komponenten

Delphi bietet Standard-Dialogkomponenten: TOpenDialog/TSaveDialog (Dateiauswahl), TOpenPictureDialog (Bildvorschau), TColorDialog, TFontDialog, TPrintDialog. Execute gibt True zurück, wenn der Benutzer auf OK geklickt hat. Filter setzt Dateityp-Muster ('Beschreibung|*.ext'). MessageDlg zeigt modale Meldungsboxen mit Typen (mtInformation, mtWarning, mtError, mtConfirmation) und Button-Sets ([mbYes, mbNo, mbOK, mbCancel]). InputBox/InputQuery erhalten Benutzertexteingaben. TPageControl verwaltet Tab-Oberflächen mit TTabSheet-Seiten. Alle Dialoge sind nicht-visuelle Komponenten, die auf dem Formular platziert werden.

delphi
// File dialogs
if OpenDialog1.Execute then
  ShowMessage('Selected: ' + OpenDialog1.FileName);

if SaveDialog1.Execute then
  ShowMessage('Save to: ' + SaveDialog1.FileName);

OpenDialog1.Filter := 'Text files (*.txt)|*.txt|All files (*.*)|*.*';
OpenDialog1.DefaultExt := 'txt';
OpenDialog1.Options := [ofFileMustExist, ofAllowMultiSelect];

// Color & Font dialogs
if ColorDialog1.Execute then
  Panel1.Color := ColorDialog1.Color;

if FontDialog1.Execute then
  Label1.Font := FontDialog1.Font;

// Message dialogs
case MessageDlg('Delete file?', mtWarning, [mbYes, mbNo, mbCancel], 0) of
  mrYes: DeleteFile('temp.txt');
  mrNo: ShowMessage('Cancelled');
end;

// InputBox & InputQuery
var Name := InputBox('Login', 'Enter name:', 'guest');
var Value: string;
if InputQuery('Settings', 'Port:', Value) then
  ShowMessage('Port: ' + Value);

// TPageControl (tabs)
var TabSheet := TTabSheet.Create(PageControl1);
TabSheet.PageControl := PageControl1;
TabSheet.Caption := 'Tab 1';
10

Ereignisgesteuerte Programmierung

Ereignisse & Ereignishandler

Ereignisse in Delphi sind Methodenzeiger (procedure of object). TNotifyEvent ist der Standard-Ereignistyp: procedure(Sender: TObject) of object. Ereignisse sind Eigenschaften — weisen Sie Handler zur Entwurfszeit (Objektinspektor) oder Laufzeit zu. Überprüfen Sie immer Assigned(), bevor Sie einen Ereignishandler aufrufen (kann nil sein, wenn nicht zugewiesen). Sender ist das Objekt, das das Ereignis ausgelöst hat. Benutzerdefinierte Ereignisse verwenden 'of object', um an Instanzmethoden zu binden. var-Parameter (wie var Key: Char in OnKeyPress) lassen Handler Werte modifizieren — setzen Sie Key := #0, um Eingaben zu unterdrücken.

delphi
// Event type declaration
type
  TNotifyEvent = procedure(Sender: TObject) of object;
  TKeyPressEvent = procedure(Sender: TObject; var Key: Char) of object;

  TCounter = class
  private
    FValue: Integer;
    FOnChange: TNotifyEvent;
    FOnThresholdReached: TThresholdEvent;
  public
    property Value: Integer read FValue write SetValue;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

procedure TCounter.SetValue(const NewValue: Integer);
begin
  if FValue <> NewValue then
  begin
    FValue := NewValue;
    if Assigned(FOnChange) then       // check before calling
      FOnChange(Self);                // trigger event
  end;
end;

// assigning handler at runtime
Counter1.OnChange := CounterChangeHandler;

procedure TForm1.CounterChangeHandler(Sender: TObject);
begin
  Label1.Caption := 'Value: ' + IntToStr((Sender as TCounter).Value);
end;

Delegates & Methodenzeiger

Methodenzeiger ('of object') tragen sowohl die Methodenadresse als auch die Objektinstanz — sie sind Closures über Self. Reguläre Prozedurzeiger (ohne 'of object') zeigen auf eigenständige Funktionen. Methodenzeiger ermöglichen Callbacks, Strategie-Muster und Ereignissysteme. Die Zuweisung Op := Calc.Add speichert die Referenz; der Aufruf Op(10, 20) ruft Calc.Add auf der Calc-Instanz auf. Anonyme Methoden (reference to function) sind eine moderne Alternative mit Closure-Semantik. Methodenzeiger sind das Rückgrat von Delphis ereignisgesteuerter VCL/FMX-Architektur.

delphi
type
  TMathFunc = function(X, Y: Integer): Integer of object;

  TCalculator = class
    function Add(X, Y: Integer): Integer;
    function Subtract(X, Y: Integer): Integer;
    function Multiply(X, Y: Integer): Integer;
  end;

function TCalculator.Add(X, Y: Integer): Integer;
begin
  Result := X + Y;
end;

// store and invoke method reference
var
  Calc: TCalculator;
  Op: TMathFunc;
begin
  Calc := TCalculator.Create;
  try
    Op := Calc.Add;           // method pointer
    ShowMessage(IntToStr(Op(10, 20)));  // 30

    Op := Calc.Subtract;
    ShowMessage(IntToStr(Op(10, 20)));  // -10
  finally
    Calc.Free;
  end;
end;

// regular procedure pointers (not of object)
type
  TSimpleFunc = function(X: Integer): Integer;
function DoubleIt(X: Integer): Integer;
begin
  Result := X * 2;
end;
var F: TSimpleFunc := DoubleIt;

Anonyme Methoden & Closures

Anonyme Methoden (reference to function/procedure) sind Inline-Closures, die Variablen aus ihrem umschließenden Gültigkeitsbereich erfassen. 'reference to'-Typen sind die moderne Alternative zu Methodenzeigern — sie erfassen Variablen per Referenz, sodass Änderungen an erfassten Variablen die Closure beeinflussen. Dies ermöglicht funktionale Muster: Map/Filter/Reduce, Callbacks und verzögerte Ausführung. TFunc<T,TResult> und TProc<T> sind generische Aliase in System.SysUtils. Anonyme Methoden sind unerlässlich für parallele Programmierung (PPL) und moderne Delphi-Idiome. Erfasste Variablen überleben ihren deklarierenden Gültigkeitsbereich.

delphi
type
  TFuncInt = reference to function(X: Integer): Integer;
  TProcStr = reference to procedure(const S: string);

procedure Apply(const Func: TFuncInt; Values: array of Integer);
var
  I: Integer;
begin
  for I := 0 to High(Values) do
    WriteLn(Func(Values[I]));
end;

var
  Multiplier: Integer;
  Double: TFuncInt;
begin
  Multiplier := 2;
  // anonymous method captures Multiplier (closure)
  Double := function(X: Integer): Integer
    begin
      Result := X * Multiplier;
    end;

  Apply(Double, [1, 2, 3, 4, 5]);   // 2, 4, 6, 8, 10

  Multiplier := 3;
  Apply(Double, [1, 2, 3]);          // 3, 6, 9 (captures by reference!)

  // anonymous procedure
  var Log: TProcStr := procedure(const S: string)
    begin
      WriteLn('[LOG] ' + S);
    end;
  Log('Hello');
end;

Nachrichtenbehandlung & Windows-Nachrichten

VCL basiert auf Windows-Nachrichten. Die 'message'-Direktive behandelt spezifische Nachrichten (WM_LBUTTONDOWN, WM_KEYDOWN usw.). Nachrichten-Records (TWMMouse, TWMKeyDown) sind typisierte Overlays auf TMessage. Rufen Sie immer inherited auf, um die Standardverarbeitung zuzulassen (außer Sie möchten die Nachricht unterdrücken). WndProc fängt ALLE Nachrichten vor dem Dispatch ab — verwenden Sie es sparslich für querschnittliche Belange. PostMessage ist asynchron (kehrt sofort zurück); SendMessage ist synchron (wartet auf den Handler). WM_USER + N definiert benutzerdefinierte Nachrichten. Dies ist die Grundlage des Windows-ereignisgesteuerten Modells.

delphi
type
  TMyForm = class(TForm)
  private
    procedure WMMouseDown(var Msg: TWMMouse); message WM_LBUTTONDOWN;
    procedure WMKeyDown(var Msg: TWMKeyDown); message WM_KEYDOWN;
    procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
  protected
    procedure WndProc(var Message: TMessage); override;
  end;

// handle specific Windows message
procedure TMyForm.WMMouseDown(var Msg: TWMMouse);
begin
  inherited;                       // call default handler
  ShowMessage(Format('Click at %d, %d', [Msg.XPos, Msg.YPos]));
end;

// intercept all messages
procedure TMyForm.WndProc(var Message: TMessage);
begin
  if Message.Msg = WM_CLOSE then
  begin
    if MessageDlg('Close?', mtConfirmation, [mbYes, mbNo], 0) = mrNo then
      Exit;                       // swallow the message
  end;
  inherited WndProc(Message);     // pass to default
end;

// post/send custom messages
const
  WM_MYMESSAGE = WM_USER + 100;

PostMessage(Handle, WM_MYMESSAGE, 0, 0);   // async, returns immediately
SendMessage(Handle, WM_MYMESSAGE, 0, 0);   // sync, waits for handler

Anwendungsereignisse & Leerlaufverarbeitung

TApplicationEvents zentralisiert App-Level-Ereignisse: OnIdle (feuert, wenn die Nachrichtenwarteschlange leer ist), OnException (globaler Ausnahmehandler), OnMinimize/OnRestore, OnHint (Statusleisten-Hinweise), OnMessage (alle Windows-Nachrichten). OnIdle mit Done := False erstellt eine enge Schleife; verwenden Sie es vorsichtig. TTimer feuert OnTimer in Intervallen (Interval in ms) — es ist nachrichtenbasiert, sodass es während blockierender Operationen nicht feuert. Application.ProcessMessages pumpt die Nachrichtenwarteschlange während langer Operationen (verhindert 'Reagiert nicht'), kann aber Reentrancy-Bugs verursachen. TThread.Queue/Synchronize marshallen UI-Aktualisierungen von Hintergrund-Threads an den Haupt-Thread.

delphi
type
  TForm1 = class(TForm)
    ApplicationEvents1: TApplicationEvents;
    procedure AppIdle(Sender: TObject; var Done: Boolean);
    procedure AppException(Sender: TObject; E: Exception);
    procedure AppMinimize(Sender: TObject);
  end;

// OnIdle - runs when app has no pending messages
procedure TForm1.AppIdle(Sender: TObject; var Done: Boolean);
begin
  Label1.Caption := 'Idle...';
  Done := True;                    // False = keep calling Idle
end;

// global exception handler
procedure TForm1.AppException(Sender: TObject; E: Exception);
begin
  LogError(E.Message);
  ShowMessage('Error: ' + E.Message);
end;

// TTimer - periodic events
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  StatusBar1.Panels[0].Text := TimeToStr(Now);
end;

// TThread.Queue / TThread.Synchronize - marshal to main thread
TThread.Queue(nil,
  procedure
  begin
    Label1.Caption := 'Updated from background';
  end);

// ProcessMessages - pump message queue
while LongOperationRunning do
begin
  DoChunk;
  Application.ProcessMessages;     // keep UI responsive
end;
11

Datenbankzugriff mit FireDAC

Verbindung & Abfragegrundlagen

FireDAC ist Delphis modernes, universelles Datenzugriffs-Framework, das SQLite, PostgreSQL, MySQL, SQL Server, Oracle, InterBase und mehr unterstützt. TFDConnection verwaltet die Datenbankverbindung (setzen Sie DriverName und Params). TFDQuery führt SQL mit Parametern aus (:param-Syntax) — verwenden Sie immer Parameter, um SQL-Injection zu verhindern. ExecSQL führt INSERT/UPDATE/DELETE/DDL aus (keine Ergebnismenge); Open führt SELECT aus (gibt einen Cursor zurück). FieldByName('col').AsString/AsInteger liest Werte. Navigieren Sie mit Next/Prev/First/Last; Eof markiert das Ende. FireDAC ersetzt die älteren dbExpress- und BDE-Technologien.

delphi
uses
  FireDAC.Comp.Client, FireDAC.Comp.DataSet, FireDAC.Stan.Param;

var
  FDConn: TFDConnection;
  Query: TFDQuery;
begin
  FDConn := TFDConnection.Create(nil);
  Query := TFDQuery.Create(nil);
  try
    // connection string (SQLite example)
    FDConn.DriverName := 'SQLite';
    FDConn.Params.Database := 'app.db';
    FDConn.Connected := True;

    Query.Connection := FDConn;

    // execute non-query (DDL/DML)
    Query.ExecSQL('CREATE TABLE IF NOT EXISTS users ' +
      '(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)');

    // parameterized insert (prevents SQL injection)
    Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:name, :age)';
    Query.ParamByName('name').AsString := 'Alice';
    Query.ParamByName('age').AsInteger := 30;
    Query.ExecSQL;

    // select with parameters
    Query.SQL.Text := 'SELECT * FROM users WHERE age > :minAge';
    Query.ParamByName('minAge').AsInteger := 18;
    Query.Open;

    while not Query.Eof do
    begin
      ShowMessage(Query.FieldByName('name').AsString);
      Query.Next;
    end;
  finally
    Query.Free;
    FDConn.Free;
  end;
end;

TFDTable & Live Bindings

TFDTable öffnet eine gesamte Tabelle (SELECT * FROM tablename) — bequem für einfaches CRUD, aber weniger effizient als TFDQuery für große Tabellen. Dataset-Navigation: First/Next/Prior/Last/MoveBy. Locate sucht nach Feldwerten (gibt True zurück, wenn gefunden). Bearbeitung: Append/Insert (neue Zeile) oder Edit (vorhanden), dann Felder setzen, dann Post (bestätigen) oder Cancel (zurücksetzen). Filter schränkt sichtbare Zeilen ein (clientseitig). IndexFieldNames sortiert Datensätze. Verbinden Sie TFDTable/TFDQuery mit TDataSource, dann mit TDBGrid/TDBEdit für datenbewusste UI. Live Bindings (FMX) bieten visuelle Bindung von Steuerelementen an Datenfelder.

delphi
var
  Table: TFDTable;
begin
  Table := TFDTable.Create(nil);
  try
    Table.Connection := FDConn;
    Table.TableName := 'users';
    Table.Open;                    // SELECT * FROM users

    // navigate
    Table.First;
    while not Table.Eof do
    begin
      ShowMessage(Table.FieldByName('name').AsString);
      Table.Next;
    end;

    // locate a record
    if Table.Locate('name', 'Alice', []) then
      ShowMessage('Found Alice');

    // edit/insert/post
    Table.Append;                  // or Insert / Edit
    Table.FieldByName('name').AsString := 'Bob';
    Table.FieldByName('age').AsInteger := 25;
    Table.Post;                    // commit to dataset

    // filter
    Table.Filter := 'age > 20';
    Table.Filtered := True;

    // index for sorting
    Table.IndexFieldNames := 'name';
  finally
    Table.Free;
  end;
end;

// connect to DBGrid via DataSource
DataSource1.DataSet := Table;
DBGrid1.DataSource := DataSource1;

Transaktionen & Batch-Operationen

Transaktionen stellen Atomizität sicher — alle Operationen erfolgreich oder keine. StartTransaction/Commit/Rollback umschließen verwandte Operationen. Ohne explizite Transaktionen auto-committed FireDAC jede Anweisung (langsam für Massen-Inserts). Array DML (Execute(count, startAt)) sendet parametrisierte Batches in einer Runde — dramatisch schneller für Massen-Inserts (10-100x Beschleunigung). Umschließen Sie Transaktionen immer mit try/except, um bei Fehlern Rollback auszuführen. Für lange Transaktionen ziehen Sie Isolationslevel (xiReadCommitted, xiRepeatableRead) in Betracht. Connection Pooling (TFDManager) verbessert Multi-Thread-Leistung.

delphi
var
  FDConn: TFDConnection;
  Query: TFDQuery;
  I: Integer;
begin
  FDConn := TFDConnection.Create(nil);
  Query := TFDQuery.Create(nil);
  try
    FDConn.DriverName := 'SQLite';
    FDConn.Params.Database := 'app.db';
    FDConn.Connected := True;
    Query.Connection := FDConn;

    // explicit transaction
    FDConn.StartTransaction;
    try
      Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:n, :a)';
      for I := 1 to 1000 do
      begin
        Query.ParamByName('n').AsString := 'User' + IntToStr(I);
        Query.ParamByName('a').AsInteger := 20 + (I mod 50);
        Query.ExecSQL;
      end;
      FDConn.Commit;               // commit all
    except
      FDConn.Rollback;             // undo all on error
      raise;
    end;

    // batch execute (Array DML - very fast)
    Query.SQL.Text := 'INSERT INTO logs (msg) VALUES (:m)';
    Query.Params.ArraySize := 100;
    for I := 0 to 99 do
      Query.Params[0].AsStrings[I] := 'Log entry ' + IntToStr(I);
    Query.Execute(100, 0);         // execute 100 times at once
  finally
    Query.Free;
    FDConn.Free;
  end;
end;

Gespeicherte Prozeduren & Metadaten

TFDStoredProc ruft gespeicherte Datenbankprozeduren auf. Setzen Sie StoredProcName und Parameter (ParamType: ptInput, ptOutput, ptInputOutput, ptResult). ExecProc führt Prozeduren aus, die keine Cursor zurückgeben; Open führt die aus, die Ergebnismengen zurückgeben. Gespeicherte Prozeduren kapseln Geschäftslogik serverseitig für Leistung und Sicherheit. TFDMetaInfoQuery fragt Datenbankschema ab (Tabellen, Spalten, Indizes, Constraints) — nützlich zum Erstellen dynamischer Tools, ORMs oder Schema-Browser. MetaInfoKind-Optionen: mkTables, mkColumns, mkIndexes, mkPrimaryKey, mkForeignKeys. FireDAC unterstützt auch Schema-Caching für Offline-Metadatenzugriff.

delphi
// call stored procedure
var
  SP: TFDStoredProc;
begin
  SP := TFDStoredProc.Create(nil);
  try
    SP.Connection := FDConn;
    SP.StoredProcName := 'get_user_by_id';
    SP.Params.ParamByName('@user_id').AsInteger := 42;

    // output parameter
    SP.Params.ParamByName('@name').ParamType := ptOutput;

    SP.ExecProc;                   // execute (no cursor)
    ShowMessage(SP.ParamByName('@name').AsString);

    // or Open if it returns a result set
    SP.Open;
    ShowMessage(SP.FieldByName('name').AsString);
  finally
    SP.Free;
  end;
end;

// metadata - list tables
var
  Meta: TFDMetaInfoQuery;
begin
  Meta := TFDMetaInfoQuery.Create(nil);
  try
    Meta.Connection := FDConn;
    Meta.MetaInfoKind := mkTables;     // or mkColumns, mkIndexes
    Meta.Open;
    while not Meta.Eof do
    begin
      ShowMessage(Meta.FieldByName('TABLE_NAME').AsString);
      Meta.Next;
    end;
  finally
    Meta.Free;
  end;
end;

FireDAC Memory Table & Local SQL

TFDMemTable ist ein In-Memory-Dataset — perfekt für Caching, temporäre Daten und Unit-Tests ohne Datenbank. Definieren Sie Felder mit FieldDefs, dann CreateDataSet. AppendRecord fügt Zeilen hinzu. Unterstützt Indizes, Filter und alle Dataset-Navigation. Local SQL (TFDLocalSQL) lässt Sie SQL-Abfragen gegen jedes TDataSet ausführen (einschließlich TFDMemTable, TClientDataSet, sogar Excel über ODBC) — ermöglicht Joins zwischen Memory-Tabellen und Datenbanktabellen. Dies ist mächtig für ETL, Berichte und das Erstellen von Datenschichten, die offline arbeiten. TFDMemTable kann auch in Binär- oder JSON-Dateien laden/speichern für Persistenz.

delphi
uses FireDAC.Comp.Client, FireDAC.Stan.Intf;

var
  MemTable: TFDMemTable;
begin
  MemTable := TFDMemTable.Create(nil);
  try
    // define schema in code
    MemTable.FieldDefs.Add('id', ftInteger);
    MemTable.FieldDefs.Add('name', ftString, 50);
    MemTable.FieldDefs.Add('salary', ftCurrency);
    MemTable.CreateDataSet;        // create in-memory table

    // populate
    MemTable.AppendRecord([1, 'Alice', 75000]);
    MemTable.AppendRecord([2, 'Bob', 68000]);
    MemTable.AppendRecord([3, 'Carol', 82000]);

    // index & filter
    MemTable.IndexFieldNames := 'salary';
    MemTable.Filter := 'salary > 70000';
    MemTable.Filtered := True;

    // Local SQL - query any TDataSet with SQL
    var LocalSQL := TFDLocalSQL.Create(nil);
    try
      LocalSQL.Connection := FDConn;  // or a dedicated connection
      LocalSQL.DataSets.AddDataSet(MemTable, 'employees');
      LocalSQL.Active := True;

      var Q := TFDQuery.Create(nil);
      try
        Q.Connection := FDConn;
        Q.Open('SELECT * FROM employees WHERE salary > :min ORDER BY name');
        // query in-memory data with full SQL!
      finally
        Q.Free;
      end;
    finally
      LocalSQL.Free;
    end;
  finally
    MemTable.Free;
  end;
end;
12

Generics & Anonyme Methoden

Generische Klassen & Methoden

Generics (eingeführt in Delphi 2009) ermöglichen typsichere Container und Algorithmen. TStack<T> funktioniert mit jedem Typ T — der Compiler generiert spezialisierte Versionen. Dies eliminiert Laufzeit-Casts (kein TObject-Casting) und fängt Typfehler zur Kompilierzeit. Generische Typparameter verwenden <T>-Syntax. Generische Methoden, Klassen, Records und Schnittstellen werden alle unterstützt. Constraints (class, constructor, interface) schränken ein, welche Typen verwendet werden können. Die RTL stellt TList<T>, TDictionary<TKey,TValue>, TQueue<T>, TStack<T>, TObjectList<T> in System.Generics.Collections bereit.

delphi
type
  TStack<T> = class
  private
    FItems: array of T;
    FCount: Integer;
  public
    procedure Push(const Value: T);
    function Pop: T;
    function Peek: T;
    function Count: Integer;
  end;

procedure TStack<T>.Push(const Value: T);
begin
  if FCount = Length(FItems) then
    SetLength(FItems, FCount * 2 + 4);
  FItems[FCount] := Value;
  Inc(FCount);
end;

function TStack<T>.Pop: T;
begin
  if FCount = 0 then
    raise Exception.Create('Stack empty');
  Dec(FCount);
  Result := FItems[FCount];
end;

// usage - type-safe, no casts
var
  IntStack: TStack<Integer>;
  StrStack: TStack<string>;
begin
  IntStack := TStack<Integer>.Create;
  IntStack.Push(42);
  IntStack.Push(99);
  ShowMessage(IntToStr(IntStack.Pop));  // 99

  StrStack := TStack<string>.Create;
  StrStack.Push('Hello');
  ShowMessage(StrStack.Pop);             // Hello
end;

Generische Constraints

Generische Constraints schränken Typparameter ein: 'class' (muss ein Klassentyp sein), 'constructor' (muss einen parameterlosen Create-Konstruktor haben — ermöglicht T.Create), 'record' (muss ein Werttyp sein), Schnittstellennamen (muss die Schnittstelle implementieren). Mehrere Constraints sind kommagetrennt. Constraints ermöglichen das Aufrufen von Methoden auf T (z. B. T.Create mit der constructor-Constraint). Ohne Constraints können Sie T nur zuweisen/vergleichen (keine Methodenaufrufe). Typinferenz lässt Sie manchmal explizite Typparameter weglassen. Constraints sind unerlässlich für das Erstellen typsicherer Frameworks und ORMs.

delphi
type
  // T must be a class
  TRepository<T: class> = class
    function Find(Id: Integer): T;
  end;

  // T must be a class with a parameterless constructor
  TFactory<T: class, constructor> = class
    function CreateInstance: T;
  end;

  // T must implement IComparable
  TSorter<T: IComparable> = class
    procedure Sort(var Arr: array of T);
  end;

  // multiple constraints
  TManager<T: class, constructor, IComparable> = class
  end;

function TFactory<T>.CreateInstance: T;
begin
  Result := T.Create;    // allowed because of 'constructor' constraint
end;

// type inference
type
  TPair<TKey, TValue> = class
    Key: TKey;
    Value: TValue;
    constructor Create(const K: TKey; const V: TValue);
  end;

var
  P: TPair<string, Integer>;
begin
  P := TPair<string, Integer>.Create('age', 30);
end;

TList<T> & TDictionary<TKey,TValue>

System.Generics.Collections stellt typsichere Container bereit: TList<T> (dynamisches Array), TDictionary<TKey,TValue> (Hash-Map), TQueue<T> (FIFO), TStack<T> (LIFO), TObjectList<T> (besitzt seine Objekte — gibt sie automatisch frei). Sort verwendet Standardvergleich; TComparer<T>.Construct erstellt benutzerdefinierte Vergleicher mit anonymen Methoden. FindIndex/Predicate-basierte Suche verwendet anonyme Funktionsprädikate. TryGetValue gibt True zurück und gibt den Wert aus, wenn gefunden (vermeidet Ausnahme). AddOrSetValue aktualisiert oder fügt ein. TObjectList<T> mit OwnsObjects := True gibt enthaltene Objekte automatisch frei, wenn die Liste freigegeben wird — verhindert Speicherlecks.

delphi
uses System.Generics.Collections, System.Generics.Defaults;

var
  List: TList<Integer>;
  Dict: TDictionary<string, Integer>;
  ObjList: TObjectList<TPerson>;
begin
  // TList<T>
  List := TList<Integer>.Create;
  try
    List.AddRange([3, 1, 4, 1, 5, 9, 2, 6]);
    List.Sort;                     // 1, 1, 2, 3, 4, 5, 6, 9
    List.BinarySearch(5, var Idx); // fast lookup in sorted list

    // custom comparer
    List.Sort(TComparer<Integer>.Construct(
      function(const L, R: Integer): Integer
      begin
        Result := R - L;           // descending
      end));

    // find with predicate
    var Found := List.FindIndex(
      function(const X: Integer): Boolean
      begin
        Result := X > 4;
      end);
  finally
    List.Free;
  end;

  // TDictionary
  Dict := TDictionary<string, Integer>.Create;
  try
    Dict.Add('apple', 5);
    Dict.Add('banana', 3);
    Dict.AddOrSetValue('apple', 10);  // update or insert

    if Dict.TryGetValue('banana', var Count) then
      ShowMessage(Count.ToString);

    // iterate
    for var Pair in Dict do
      ShowMessage(Pair.Key + ': ' + Pair.Value.ToString);
  finally
    Dict.Free;
  end;
end;

Anonyme Methoden als Callbacks

Anonyme Methoden ermöglichen funktionale Programmierung in Delphi. 'reference to function'-Typen sind Closures — sie erfassen Variablen aus ihrem umschließenden Gültigkeitsbereich. Higher-Order-Funktionen wie Map und Filter nehmen Funktionen als Parameter, was prägnante Datentransformationen ermöglicht. TFunc<T,TResult> und TProc<T> sind eingebaute generische Delegate-Typen. Closures erfassen Variablen per Referenz, sodass sie spätere Änderungen widerspiegeln. Dieses Muster ersetzt ausführliche Callback-Schnittstellen und ist unerlässlich für PPL (Parallel Programming Library), Event-Handler und LINQ-ähnliche Operationen. Anonyme Methoden sind referenzzählend und werden automatisch verwaltet.

delphi
uses System.SysUtils;

type
  TFunc<T, TResult> = reference to function(Arg: T): TResult;
  TProc<T> = reference to procedure(Arg: T);

// higher-order functions
function Map<T, TResult>(const Source: array of T;
  const Mapper: TFunc<T, TResult>): TArray<TResult>;
var
  I: Integer;
begin
  SetLength(Result, Length(Source));
  for I := 0 to High(Source) do
    Result[I] := Mapper(Source[I]);
end;

function Filter<T>(const Source: array of T;
  const Predicate: TFunc<T, Boolean>): TArray<T>;
var
  I, Count: Integer;
begin
  Count := 0;
  SetLength(Result, Length(Source));
  for I := 0 to High(Source) do
    if Predicate(Source[I]) then
    begin
      Result[Count] := Source[I];
      Inc(Count);
    end;
  SetLength(Result, Count);
end;

// usage with closures
var
  Numbers: array of Integer;
  Doubled, Evens: TArray<Integer>;
  Threshold: Integer;
begin
  Numbers := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  Threshold := 5;

  Doubled := Map<Integer, Integer>(Numbers,
    function(X: Integer): Integer
    begin
      Result := X * 2;
    end);

  // closure captures Threshold
  Evens := Filter<Integer>(Numbers,
    function(X: Integer): Boolean
    begin
      Result := (X > Threshold) and (X mod 2 = 0);
    end);
end;

Generische Schnittstellen & TComparer

Generische Schnittstellen ermöglichen typsichere Verträge: IRepository<T> funktioniert mit jedem Entitätstyp. Kombiniert mit Referenzzählung (TInterfacedObject) bietet dies automatische Speicherverwaltung — Schnittstellen sind referenzzählend, werden freigegeben, wenn die letzte Referenz fällt. TComparer<T>.Construct erstellt einen IComparer<T> aus einer anonymen Vergleichsfunktion — verwendet von Sort, BinarySearch und SortedDictionary. Generische Schnittstellen sind die Grundlage von Dependency Injection in Delphi (registrieren Sie IRepository<TUser>, injizieren Sie in Services). Das Spring4D-Framework erweitert dies mit einem vollständigen DI-Container. Generische Constraints (class, constructor) stellen sicher, dass T instanziiert werden kann.

delphi
type
  IComparable<T> = interface
    function CompareTo(const Other: T): Integer;
  end;

  IRepository<T> = interface
    function GetById(Id: Integer): T;
    function GetAll: TArray<T>;
    procedure Save(const Entity: T);
    procedure Delete(Id: Integer);
  end;

  TMemoryRepository<T: class, constructor> = class(TInterfacedObject, IRepository<T>)
  private
    FItems: TList<T>;
  public
    constructor Create;
    destructor Destroy; override;
    function GetById(Id: Integer): T;
    function GetAll: TArray<T>;
    procedure Save(const Entity: T);
    procedure Delete(Id: Integer);
  end;

// custom comparer for sorting objects
type
  TPerson = class
    Name: string;
    Age: Integer;
  end;

var
  People: TObjectList<TPerson>;
begin
  People := TObjectList<TPerson>.Create;
  People.Sort(TComparer<TPerson>.Construct(
    function(const L, R: TPerson): Integer
    begin
      Result := CompareText(L.Name, R.Name);  // sort by name
    end));
end;
13

RTTI & Reflection

Erweiterte RTTI-Grundlagen

Erweiterte RTTI (Runtime Type Information), eingeführt in Delphi 2010, bietet vollständige Reflection: Typen, Eigenschaften, Methoden und Felder zur Laufzeit inspizieren. TRTTIContext ist der Einstiegspunkt. GetType gibt TRttiType für eine Klasse zurück. GetProperties enumeriert veröffentlichte Eigenschaften. GetValue/SetValue liest/schreibt Eigenschaftswerte dynamisch mit TValue (ein variant-ähnlicher Typ). Nur 'published'-Member haben standardmäßig RTTI (verwenden Sie {$RTTI EXPLICIT ...}-Direktive für mehr). RTTI treibt Serialisierung (JSON/XML), ORMs, Dependency Injection und visuelle Designer. Es hat einen kleinen Leistungsaverlust, ermöglicht aber mächtiges Metaprogrammieren.

delphi
uses System.RTTI, System.TypInfo;

type
  TPerson = class
  private
    FName: string;
    FAge: Integer;
  published
    property Name: string read FName write FName;
    property Age: Integer read FAge write FAge;
  end;

var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Person: TPerson;
begin
  Person := TPerson.Create;
  try
    Person.Name := 'Alice';
    Person.Age := 30;

    Ctx := TRTTIContext.Create;
    try
      RType := Ctx.GetType(TPerson);

      // enumerate properties
      for Prop in RType.GetProperties do
      begin
        WriteLn(Prop.Name, ': ', Prop.PropertyType.Name);

        // read value
        if Prop.IsReadable then
          WriteLn('  Value: ', Prop.GetValue(Person).ToString);

        // write value
        if Prop.IsWritable then
          Prop.SetValue(Person, TValue.From<string>('Bob'));
      end;

      // get specific property
      Prop := RType.GetProperty('Name');
      ShowMessage(Prop.GetValue(Person).AsString);
    finally
      Ctx.Free;
    end;
  finally
    Person.Free;
  end;
end;

Methodenaufruf & Attribute

RTTI kann Methoden dynamisch über TRttiMethod.Invoke aufrufen — übergeben Sie Argumente als TValue-Array. Attribute (TCustomAttribute-Unterklassen) hängen Metadaten an Typen, Eigenschaften und Methoden mit [Attribute]-Syntax an. GetAttributes ruft sie zur Laufzeit ab. Dies ermöglicht Validierungs-Frameworks ([Required], [MaxLength]), ORM-Mapping ([Table], [Column]) und Serialisierungssteuerung ([JsonProperty]). Attribute sind ein mächtiges Metaprogrammierungs-Feature — der Compiler speichert sie in RTTI, und Frameworks lesen sie, um Verhalten zu steuern. Methodenaufruf über RTTI ist langsamer als direkte Aufrufe, aber unerlässlich für Skripting, DI und dynamischen Dispatch.

delphi
uses System.RTTI;

type
  TValidatorAttribute = class(TCustomAttribute)
  private
    FMaxLen: Integer;
  public
    constructor Create(MaxLen: Integer);
    property MaxLen: Integer read FMaxLen;
  end;

  TUser = class
  private
    FName: string;
  public
    [Validator(50)]
    property Name: string read FName write FName;

    function Greet(const Greeting: string): string;
  end;

constructor TValidatorAttribute.Create(MaxLen: Integer);
begin
  FMaxLen := MaxLen;
end;

var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Attr: TCustomAttribute;
  Method: TRttiMethod;
  User: TUser;
  Result: TValue;
begin
  User := TUser.Create;
  User.Name := 'Alice';
  Ctx := TRTTIContext.Create;
  try
    RType := Ctx.GetType(TUser);

    // read attributes
    Prop := RType.GetProperty('Name');
    for Attr in Prop.GetAttributes do
    begin
      if Attr is TValidatorAttribute then
        WriteLn('Max length: ', TValidatorAttribute(Attr).MaxLen);
    end;

    // invoke method by name
    Method := RType.GetMethod('Greet');
    Result := Method.Invoke(User, ['Hello']);
    ShowMessage(Result.AsString);  // Hello, Alice
  finally
    Ctx.Free;
    User.Free;
  end;
end;

Typ-Entdeckung & Enumeration

TRTTIContext.GetTypes enumeriert alle Typen mit RTTI im kompilierten Programm — nützlich für Plugin-Entdeckung, ORM-Modell-Scanning und das Erstellen von Typ-Browsern. FindType lokalisiert einen Typ nach qualifiziertem Namen ('UnitName.TypeName'). TRttiType bietet GetFields (alle Felder), GetMethods (alle Methoden), GetProperties (veröffentlichte Eigenschaften). TypeKind unterscheidet Klassen, Records, Schnittstellen, Enums usw. AsInstance.MetaclassType gibt die Klassenreferenz für Instanziierung. Dies ermöglicht Frameworks, die Komponenten automatisch entdecken und verdrahten. Die Spring4D- und DORM-Frameworks verwenden dies für automatisches ORM-Mapping. RTTI-Enumeration ist langsam — zwischenspeichern Sie Ergebnisse für wiederholte Verwendung.

delphi
uses System.RTTI;

var
  Ctx: TRTTIContext;
  Types: TArray<TRttiType>;
  T: TRttiType;
  Field: TRttiField;
  Method: TRttiMethod;
begin
  Ctx := TRTTIContext.Create;
  try
    // enumerate ALL types in the program
    Types := Ctx.GetTypes;

    // find types by name
    T := Ctx.FindType('Unit1.TPerson');
    if T <> nil then
      ShowMessage('Found: ' + T.QualifiedName);

    // filter: all classes in a unit
    for T in Types do
    begin
      if (T.TypeKind = tkClass) and T.QualifiedName.StartsWith('MyApp.') then
      begin
        WriteLn('Class: ', T.Name);

        // enumerate fields
        for Field in T.GetFields do
          WriteLn('  Field: ', Field.Name, ': ', Field.FieldType.Name);

        // enumerate methods
        for Method in T.GetMethods do
          WriteLn('  Method: ', Method.Name,
            ' - ', Method.MethodType.ToString);
      end;
    end;

    // create instance via RTTI
    var Instance := T.AsInstance.MetaclassType.Create;
    try
      // use instance...
    finally
      Instance.Free;
    end;
  finally
    Ctx.Free;
  end;
end;

TValue & Dynamische Typisierung

TValue ist Delphis dynamischer Werttyp — eine getaggte Union, die jeden Typ mit seinen Typinformationen hält. From<T> umschließt einen Wert; AsType<T>/AsInteger/AsString entpacken ihn. IsType<T> prüft den Typ. TryAsType versucht sichere Konvertierung. TValue ist unerlässlich für RTTI (Eigenschaftswerte, Methodenargumente) und ermöglicht dynamische Typisierung in einer statisch typisierten Sprache. Es ist ähnlich wie C#'s 'object' mit Typ-Info oder Pythons dynamische Natur. TValue verarbeitet Primitive, Strings, Objekte, Arrays und Records. Verwenden Sie es beim Erstellen von Serialisierern, Skript-Engines oder generischen Datenschichten. Es hat Overhead gegenüber direkter Typisierung, bietet aber maximale Flexibilität.

delphi
uses System.RTTI;

var
  V: TValue;
  I: Integer;
  S: string;
  D: Double;
  Obj: TObject;
begin
  // wrap values
  V := TValue.From<Integer>(42);
  ShowMessage(V.ToString);          // '42'
  I := V.AsInteger;                 // unwrap

  V := TValue.From<string>('Hello');
  S := V.AsString;

  // type checking
  if V.IsType<string> then
    ShowMessage('It is a string');

  // conversion
  V := TValue.From<Integer>(100);
  D := V.AsExtended;                // 100.0

  // boxing objects
  var Person := TPerson.Create;
  try
    V := TValue.From<TPerson>(Person);
    if V.IsObject then
      ShowMessage(V.AsObject.ClassName);  // 'TPerson'
  finally
    Person.Free;
  end;

  // array of TValue for method invocation
  var Args: array of TValue;
  SetLength(Args, 2);
  Args[0] := TValue.From<Integer>(10);
  Args[1] := TValue.From<Integer>(20);

  // try conversion
  V := TValue.From<string>('123');
  if V.TryAsType<Integer>(I) then
    ShowMessage(IntToStr(I));       // 123
end;

Serialisierung mit RTTI

RTTI ermöglicht automatische Serialisierung — Konvertieren von Objekten in/aus JSON, XML oder jedem Format ohne manuelles Mapping-Code. ObjectToJSON iteriert veröffentlichte Eigenschaften, liest Werte über RTTI und erstellt ein TJSONObject. JSONToObject kehrt den Prozess um. Dieses Muster treibt REST-Clients, Konfigurationssysteme und ORM-Schichten. Die REST.Json-Unit stellt TJson.ObjectToJsonString und TJson.JsonToObject dafür out of the box bereit. Für Produktion verwenden Sie Attribute ([JsonProperty('name')]), um Feldnamen zu steuern, und behandeln Sie verschachtelte Objekte, Arrays und benutzerdefinierte Typen. RTTI-basierte Serialisierung ist langsamer als handgeschriebene Mapper, aber viel wartbarer.

delphi
uses System.RTTI, System.JSON;

function ObjectToJSON(Obj: TObject): TJSONObject;
var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Val: TValue;
begin
  Result := TJSONObject.Create;
  Ctx := TRTTIContext.Create;
  try
    RType := Ctx.GetType(Obj.ClassType);
    for Prop in RType.GetProperties do
    begin
      if not Prop.IsReadable then Continue;
      Val := Prop.GetValue(Obj);
      case Val.Kind of
        tkString, tkUString:
          Result.AddPair(Prop.Name, Val.AsString);
        tkInteger:
          Result.AddPair(Prop.Name, TJSONNumber.Create(Val.AsInteger));
        tkFloat:
          Result.AddPair(Prop.Name, TJSONNumber.Create(Val.AsExtended));
        tkEnumeration:
          Result.AddPair(Prop.Name, TJSONBool.Create(Val.AsBoolean));
      end;
    end;
  finally
    Ctx.Free;
  end;
end;

procedure JSONToObject(Obj: TObject; const JSON: TJSONObject);
var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Pair: TJSONPair;
begin
  Ctx := TRTTIContext.Create;
  try
    RType := Ctx.GetType(Obj.ClassType);
    for Prop in RType.GetProperties do
    begin
      if not Prop.IsWritable then Continue;
      Pair := JSON.FindPair(Prop.Name);
      if Pair <> nil then
      begin
        case Prop.PropertyType.TypeKind of
          tkString, tkUString:
            Prop.SetValue(Obj, TValue.From<string>(Pair.JsonValue.Value));
          tkInteger:
            Prop.SetValue(Obj, TValue.From<Integer>((Pair.JsonValue as TJSONNumber).AsInt));
        end;
      end;
    end;
  finally
    Ctx.Free;
  end;
end;
14

Schnittstellen & COM

Schnittstellen-Grundlagen & Referenzzählung

Schnittstellen definieren Verträge (Methodensignaturen) ohne Implementierung. TInterfacedObject bietet Referenzzählung — wenn die letzte Schnittstellenreferenz fällt, wird das Objekt automatisch freigegeben (kein Free-Aufruf nötig). Dies ist Delphis automatische Speicherverwaltung für Schnittstellen-Objekte. GUIDs (['{...}']) ermöglichen COM-Interop und InterfaceAs/Supports-Prüfungen. Eine Klasse kann mehrere Schnittstellen implementieren (TShape implementiert sowohl IMovable als auch IDrawable). Schnittstellen-Eigenschaften sind erlaubt (müssen Lese-/Schreibmethoden haben). Verwenden Sie immer Schnittstellentypen (IMovable) nicht Klassentypen (TShape), damit Referenzzählung funktioniert. Das Mischen von Objekt- und Schnittstellenreferenzen kann vorzeitige Freigabe verursachen.

delphi
type
  IMovable = interface
    ['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']  // GUID (optional)
    procedure MoveTo(X, Y: Integer);
    function GetPosition: TPoint;
    property Position: TPoint read GetPosition;
  end;

  IDrawable = interface
    procedure Draw(Canvas: TCanvas);
  end;

  TShape = class(TInterfacedObject, IMovable, IDrawable)
  private
    FX, FY: Integer;
  public
    procedure MoveTo(X, Y: Integer);
    function GetPosition: TPoint;
    procedure Draw(Canvas: TCanvas);
  end;

procedure TShape.MoveTo(X, Y: Integer);
begin
  FX := X;
  FY := Y;
end;

// usage - reference counted automatically
var
  Shape: IMovable;
begin
  Shape := TShape.Create;          // ref count = 1
  Shape.MoveTo(100, 200);
  ShowMessage(Format('%d, %d', [Shape.Position.X, Shape.Position.Y]));
  // when Shape goes out of scope, ref count drops to 0, object freed
end;

Dependency Injection mit Schnittstellen

Schnittstellen ermöglichen Dependency Injection — übergeben Sie Abhängigkeiten (ILogger, IUserDataAccess) durch Konstruktoren, anstatt sie fest zu codieren. Dies entkoppelt TUserService von konkreten Implementierungen: tauschen Sie TConsoleLogger gegen TFileLogger aus, ohne TUserService zu ändern. TUserService selbst ist nicht referenzzählend (erbt von TObject, nicht TInterfacedObject), sodass es manuelles Free benötigt. Für vollständiges DI verwenden Sie einen Container (Spring4D, DSharp), der Abhängigkeiten nach Schnittstellentyp auflöst: Container.RegisterType<ILogger, TConsoleLogger>; Container.Build; Service := Container.Resolve<TUserService>. DI verbessert Testbarkeit (Mocks injizieren), Wartbarkeit und Modularität. Hängen Sie immer an Abstraktionen (Schnittstellen), nicht an Konkretionen.

delphi
type
  ILogger = interface
    procedure Log(const Msg: string);
  end;

  IUserDataAccess = interface
    function GetUser(Id: Integer): string;
  end;

  TConsoleLogger = class(TInterfacedObject, ILogger)
    procedure Log(const Msg: string);
  end;

  TDatabaseAccess = class(TInterfacedObject, IUserDataAccess)
    function GetUser(Id: Integer): string;
  end;

  TUserService = class
  private
    FLogger: ILogger;
    FDataAccess: IUserDataAccess;
  public
    constructor Create(ALogger: ILogger; ADataAccess: IUserDataAccess);
    function GetUserName(Id: Integer): string;
  end;

constructor TUserService.Create(ALogger: ILogger; ADataAccess: IUserDataAccess);
begin
  FLogger := ALogger;
  FDataAccess := ADataAccess;
end;

function TUserService.GetUserName(Id: Integer): string;
begin
  FLogger.Log('Fetching user ' + IntToStr(Id));
  Result := FDataAccess.GetUser(Id);
end;

// wire up dependencies (manual DI)
var
  Logger: ILogger;
  DataAccess: IUserDataAccess;
  Service: TUserService;
begin
  Logger := TConsoleLogger.Create;
  DataAccess := TDatabaseAccess.Create;
  Service := TUserService.Create(Logger, DataAccess);
  try
    ShowMessage(Service.GetUserName(42));
  finally
    Service.Free;    // TUserService is not ref-counted (not TInterfacedObject)
  end;
end;

COM-Interop

COM (Component Object Model) lässt Delphi mit Windows-Anwendungen und -Bibliotheken interagieren. CreateOleObject erstellt COM-Objekte über spätes Binden (Variant-Typ — keine Kompilierzeit-Prüfung, aber einfach). Import Type Library generiert früh-gebundene Units mit typisierten Schnittstellen (IntelliSense, Typprüfung, bessere Leistung). IUnknown ist die Basis-COM-Schnittstelle mit AddRef/Release/QueryInterface für Referenzzählung. stdcall ist die COM-Aufrufkonvention. CoCreateInstance ist die niederschwellige API. Häufige COM-Verwendungen: Office-Automatisierung (Excel, Word), ADO (Datenbank), Shell-Integration, WMI-Abfragen. Rufen Sie immer CoInitialize vor COM-Operationen in Threads auf. COM-Objekte sind apartment-threaded — marshallen Sie sorgfältig zwischen Threads.

delphi
uses
  Winapi.ActiveX, System.Win.ComObj;

// create COM object (e.g., Excel)
var
  Excel: Variant;
  Workbook: Variant;
  Sheet: Variant;
begin
  Excel := CreateOleObject('Excel.Application');
  try
    Excel.Visible := True;
    Workbook := Excel.Workbooks.Add;
    Sheet := Workbook.Worksheets[1];

    // write data
    Sheet.Cells[1, 1].Value := 'Name';
    Sheet.Cells[1, 2].Value := 'Score';
    Sheet.Cells[2, 1].Value := 'Alice';
    Sheet.Cells[2, 2].Value := 95;

    // formula
    Sheet.Cells[3, 2].Value := '=AVERAGE(B2:B2)';

    Workbook.SaveAs('C:\report.xlsx');
  finally
    Excel.Quit;
  end;
end;

// import type library for early binding
// Component → Import Component → Import Type Library
// generates a unit with typed interfaces (early binding, IntelliSense)

// IUnknown - base COM interface
type
  IMyComObject = interface(IUnknown)
    ['{...}']
    function DoSomething: HResult; stdcall;
  end;

// CoCreateInstance for low-level COM
var
  Obj: IUnknown;
  MyObj: IMyComObject;
begin
  CoCreateInstance(CLASS_MyComObject, nil, CLSCTX_INPROC_SERVER,
    IMyComObject, MyObj);
  MyObj.DoSomething;
end;

Implements & Aggregation

Die 'implements'-Direktive delegiert eine Schnittstelle an eine Eigenschaft — Komposition über Vererbung. TDataService legt ICache frei, indem es an FCache (einen TMemoryCache) delegiert. Dies ist sauberer als Vererbung und lässt Sie Verhalten mischen und kombinieren. Supports() prüft, ob ein Objekt eine Schnittstelle implementiert (verwendet intern QueryInterface). Der as-Operator führt einen geprüften Schnittstellen-Cast durch. Schnittstellen-Delegation ermöglicht das Decorator-Muster (Cache mit Logging umwickeln), Strategie-Muster (Cache-Implementierungen austauschen) und saubere Trennung der Belange. COM's QueryInterface ist der zugrunde liegende Mechanismus — jedes Schnittstellen-Objekt kann nach jeder Schnittstelle abgefragt werden, die es unterstützt.

delphi
type
  ICache = interface
    function Get(const Key: string): string;
    procedure Put(const Key, Value: string);
  end;

  TMemoryCache = class(TInterfacedObject, ICache)
  private
    FDict: TDictionary<string, string>;
  public
    constructor Create;
    destructor Destroy; override;
    function Get(const Key: string): string;
    procedure Put(const Key, Value: string);
  end;

  TDataService = class(TInterfacedObject, ICache)
  private
    FCache: ICache;
  public
    constructor Create(ACache: ICache);
    // 'implements' delegates ICache to FCache
    property Cache: ICache read FCache implements ICache;
  end;

// usage - TDataService exposes ICache via delegation
var
  Service: ICache;
begin
  Service := TDataService.Create(TMemoryCache.Create);
  Service.Put('key1', 'value1');    // delegates to TMemoryCache
  ShowMessage(Service.Get('key1'));
end;

// QueryInterface / Supports
var
  Obj: TInterfacedObject;
  Intf: ICache;
begin
  Obj := TMemoryCache.Create;
  if Supports(Obj, ICache, Intf) then
    Intf.Put('a', 'b');
end;

Schwache & Unsafe-Referenzen

Referenzzählung kann Speicherlecks mit zirkulären Referenzen verursachen (parent↔child). [Weak] bricht Zyklen — es verfolgt die Referenz, inkrementiert aber nicht den Ref-Count und wird automatisch genillt, wenn das Ziel freigegeben wird. [Unsafe] ist ein roher Zeiger (keine Verfolgung, kein Ref-Count) — am schnellsten, aber gefährlich (Dangling-Pointer). Verwenden Sie [Weak] für Parent-/Back-Referenzen, Observer-Muster und Ereignisabonnements. Die Standard- (starke) Referenz inkrementiert den Ref-Count und hält das Objekt am Leben. Delphis ARC (zugunsten von [Weak] veraltet) handhabte dies früher automatisch auf Mobile. Auf Desktop verwenden Schnittstellen manuelle Referenzzählung — [Weak] ist unerlässlich für zyklusfreie Designs. Paaren Sie starke und schwache Referenzen immer korrekt.

delphi
type
  TParent = class;
  TChild = class;

  TParent = class(TInterfacedObject)
  private
    FChild: TChild;
    procedure ChildCallback(const Msg: string);
  public
    destructor Destroy; override;
    property Child: TChild read FChild;
  end;

  TChild = class(TInterfacedObject)
  private
    // [Weak] avoids circular reference counting (parent-child cycle)
    [Weak] FParent: TParent;
    FCallback: TProc<string>;
  public
    constructor Create(AParent: TParent);
    property Parent: TParent read FParent;
  end;

  // [Unsafe] - raw pointer, no ref counting at all
  // [Weak] - tracked but doesn't increment ref count
  // (default) - strong reference, increments ref count

destructor TParent.Destroy;
begin
  FChild := nil;  // releases strong ref
  inherited;
end;

// without [Weak], this creates a memory leak:
// Parent holds Child (ref=1), Child holds Parent (ref=1)
// neither ref count ever reaches 0 → leak

var
  Parent: TParent;
begin
  Parent := TParent.Create;
  // when Parent goes out of scope, both are freed correctly
end;
15

Multithreading & PPL

TThread-Grundlagen

TThread ist die Grundlage von Delphi-Multithreading. Überschreiben Sie Execute mit der Hintergrundarbeit. Prüfen Sie Terminated periodisch für graceful Abbruch. TThread.Synchronize führt Code auf dem Haupt-Thread aus (blockierend — wartet auf Fertigstellung); TThread.Queue ist asynchron (postet und kehrt sofort zurück). GREIFEN Sie NIEMALS auf UI-Steuerelemente von Hintergrund-Threads zu — verwenden Sie immer Synchronize oder Queue. FreeOnTerminate := True gibt den Thread automatisch frei, wenn Execute endet. CreateAnonymousThread erstellt einen Einweg-Thread aus einer anonymen Methode — bequem für einfache Aufgaben. Für Produktionscode bevorzugen Sie PPL (TTask) gegenüber rohem TThread für bessere Komposition und Fehlerbehandlung.

delphi
type
  TWorkerThread = class(TThread)
  private
    FResult: Integer;
  protected
    procedure Execute; override;
  public
    property Result: Integer read FResult;
  end;

procedure TWorkerThread.Execute;
var
  I: Integer;
begin
  FResult := 0;
  for I := 1 to 100 do
  begin
    if Terminated then Break;       // check for cancellation
    FResult := FResult + I;
    Sleep(10);                      // simulate work
  end;

  // update UI from background thread
  TThread.Synchronize(nil,
    procedure
    begin
      Form1.Label1.Caption := 'Done: ' + IntToStr(FResult);
    end);
end;

// create and run
var
  Worker: TWorkerThread;
begin
  Worker := TWorkerThread.Create(True);  // suspended
  Worker.FreeOnTerminate := True;        // auto-free when done
  Worker.Start;                          // begin execution
end;

// TThread.CreateAnonymousThread - quick one-off
TThread.CreateAnonymousThread(
  procedure
  var I: Integer;
  begin
    for I := 1 to 10 do
      TThread.Queue(nil,
        procedure
        begin
          Form1.Label1.Caption := IntToStr(I);
        end);
  end).Start;

Parallel Programming Library (PPL)

Die Parallel Programming Library (PPL) in System.Threading bietet hochrangige Nebenläufigkeit: TTask (Fire-and-Forget-Async), TTask.Future<T> (Async mit Rückgabewert) und parallele Schleifen. Tasks verwenden automatisch den Thread-Pool — keine Thread-Verwaltung nötig. WaitForAll/WaitForAny komponieren mehrere Tasks. Future.Value blockiert, bis das Ergebnis bereit ist (wie ein Promise). PPL ist die moderne Alternative zu rohem TThread — sauberer, komponierbar und integriert in Async/Await-Muster. Tasks erfassen Ausnahmen und werfen sie erneut, wenn Sie auf .Value zugreifen, was ordnungsgemäße Fehlerweitergabe ermöglicht. Verwenden Sie TEvent/TCountdownEvent für feingranulare Synchronisation zwischen Tasks.

delphi
uses System.Threading, System.SyncObjs;

// TTask - async operations
var
  Task: ITask;
begin
  Task := TTask.Create(
    procedure
    begin
      Sleep(2000);  // simulate work
      TThread.Queue(nil,
        procedure
        begin
          ShowMessage('Task done');
        end);
    end);
  Task.Start;
end;

// TTask.WaitForAll - wait for multiple tasks
var
  Tasks: array of ITask;
begin
  SetLength(Tasks, 3);
  Tasks[0] := TTask.Create(procedure begin DownloadFile('a.txt'); end);
  Tasks[1] := TTask.Create(procedure begin DownloadFile('b.txt'); end);
  Tasks[2] := TTask.Create(procedure begin DownloadFile('c.txt'); end);

  for var T in Tasks do T.Start;

  // wait for all (with timeout)
  if TTask.WaitForAll(Tasks, 30000) then
    ShowMessage('All downloads complete')
  else
    ShowMessage('Timeout');
end;

// TTask.Future<T> - async with return value
var
  Future: IFuture<string>;
begin
  Future := TTask.Future<string>(
    function: string
    begin
      Result := FetchDataFromServer;  // long operation
    end);

  // do other work...
  ShowMessage('Result: ' + Future.Value);  // blocks until ready
end;

Parallel For & Schleifen

TParallel.For parallelisiert Schleifen über den Thread-Pool — Iterationen laufen gleichzeitig auf mehreren Kernen. Verwenden Sie &For (escaped Keyword), da 'for' reserviert ist. Für CPU-gebundene Schleifen mit unabhängigen Iterationen kann dies nahezu lineare Beschleunigung auf Multi-Core-Maschinen geben. KRITISCH: gemeinsamer Zustand (wie Sum) muss mit Sperren (TCriticalSection) geschützt werden oder verwenden Sie TInterlocked.Increment für atomare Operationen. State.Break stoppt die Schleife (wie break). State.ShouldExit prüft, ob Break aufgerufen wurde. Vermeiden Sie das Parallelisieren von Schleifen mit wenigen Iterationen oder schwerer E/A (Thread-Pool wird erschöpft). Stride steuert Iterationsschrittweite. Verschachtelte parallele Schleifen helfen selten — die äußere Schleife sättigt bereits die Kerne.

delphi
uses System.Threading, System.SyncObjs;

// TParallel.For - parallelized loop
var
  Sum: Integer;
  Lock: TCriticalSection;
  I: Integer;
begin
  Sum := 0;
  Lock := TCriticalSection.Create;
  try
    TParallel.&For(1, 1000000,
      procedure(Index: Integer)
      begin
        // thread-safe accumulation
        Lock.Enter;
        try
          Sum := Sum + Index;
        finally
          Lock.Leave;
        end;
      end);
    ShowMessage('Sum: ' + IntToStr(Sum));
  finally
    Lock.Free;
  end;
end;

// with stride and state
TParallel.&For(1, 100,
  procedure(Index: Integer; var State: TParallelLoopState)
  begin
    if Index = 50 then
      State.Break;                  // stop after current iterations
    // process Index...
  end);

// TParallel.For with step (stride)
TParallel.&For(0, 99, 2,    // 0, 2, 4, 6, ...
  procedure(Index: Integer)
  begin
    ProcessEven(Index);
  end);

// nested parallel loops (use sparingly)
TParallel.&For(0, 9,
  procedure(I: Integer)
  begin
    TParallel.&For(0, 9,
      procedure(J: Integer)
      begin
        Matrix[I, J] := Compute(I, J);
      end);
  end);

Synchronisationsprimitive

System.SyncObjs stellt Synchronisationsprimitive bereit: TCriticalSection (Mutex — nur ein Thread betritt gleichzeitig), TEvent (Signal zwischen Threads — SetEvent weckt, WaitFor blockiert), TMonitor (jedes Objekt sperren — wie Java/C#-Monitore mit Wait/Pulse), TInterlocked (atomares Increment/Decrement/Exchange/CompareExchange — lock-frei). TCriticalSection ist das häufigste — paaren Sie Enter/Leave immer mit try/finally. TEvent.WaitFor gibt wrSignaled, wrTimeout oder wrAbandoned zurück. TMonitor.Wait gibt die Sperre temporär frei und blockiert; Pulse/PulseAll wecken Wartende. TInterlocked ist am schnellsten für einfache Zähler — kein Lock-Overhead. Wählen Sie das richtige Primitive: CriticalSection für exklusiven Zugriff, Event für Signalisierung, Interlocked für atomare Zähler.

delphi
uses System.SyncObjs;

// TCriticalSection - mutual exclusion
var
  CS: TCriticalSection;
begin
  CS := TCriticalSection.Create;
  try
    CS.Enter;
    try
      // exclusive access to shared data
    finally
      CS.Leave;
    end;
  finally
    CS.Free;
  end;
end;

// TEvent - signaling between threads
var
  Event: TEvent;
begin
  Event := TEvent.Create(nil, True, False, '');  // manual reset
  try
    // thread 1: wait
    if Event.WaitFor(5000) = wrSignaled then
      ShowMessage('Signaled');

    // thread 2: signal
    Event.SetEvent;    // wake waiting threads
    Event.ResetEvent;  // clear signal
  finally
    Event.Free;
  end;
end;

// TMonitor - lock any object
var
  List: TList<Integer>;
begin
  TMonitor.Enter(List);
  try
    List.Add(42);
  finally
    TMonitor.Exit(List);
  end;

  // TMonitor.Wait / Pulse (like Java wait/notify)
  TMonitor.Enter(List);
  try
    while List.Count = 0 do
      TMonitor.Wait(List, 1000);  // release lock, wait
    TMonitor.PulseAll(List);      // wake waiting threads
  finally
    TMonitor.Exit(List);
  end;
end;

// TInterlocked - atomic operations
TInterlocked.Increment(Counter);
TInterlocked.Exchange(Value, 42);

Thread-Pool & Async/Await-Muster

TThreadPool verwaltet einen Pool von Worker-Threads — Wiederverwendung von Threads vermeidet Erstellungsaufwand. Setzen Sie min/max-Threads basierend auf Ihrer Arbeitslast (CPU-gebunden: ~Kernanzahl, E/A-gebunden: mehr). TTask.Run ist Abkürzung für Create+Start. ContinueWith verkettet Tasks — läuft, nachdem der Vorgänger abschließt, was Pipelines ermöglicht. Task.Status (Created, WaitingToRun, Running, Completed, Canceled, Faulted) verfolgt den Lebenszyklus. ICancellation ermöglicht kooperativen Abbruch — prüfen Sie IsCancelled periodisch in langen Tasks. Für echtes Async/Await hat Delphi kein Sprach-Level-Await, aber TTask.Future + .Value bietet äquivalente Semantik. Die OmniThreadLibrary (OTL) bietet höherwertige Abstraktionen (Pipelines, Message Passing) aufgebaut auf PPL.

delphi
uses System.Threading, System.SyncObjs;

// configure thread pool
var
  Pool: TThreadPool;
begin
  Pool := TThreadPool.Create;
  try
    Pool.SetMaxWorkerThreads(8);
    Pool.SetMinWorkerThreads(2);

    // use pool with TTask
    TTask.Run(
      procedure
      begin
        // runs on the pool
      end, Pool);
  finally
    Pool.Free;
  end;
end;

// async/await pattern using futures
function FetchDataAsync: IFuture<TStrings>;
begin
  Result := TTask.Future<TStrings>(
    function: TStrings
    begin
      Result := TStringList.Create;
      // simulate slow fetch
      TThread.Sleep(2000);
      Result.LoadFromFile('data.txt');
    end);
end;

// chain tasks
var
  Task1, Task2: ITask;
begin
  Task1 := TTask.Run(
    procedure
    begin
      DownloadFile('part1.zip');
    end);

  // Task2 runs after Task1 completes
  Task2 := Task1.ContinueWith(
    procedure(const ATask: ITask)
    begin
      if ATask.Status = TTaskStatus.Completed then
        ProcessFile('part1.zip')
      else
        ShowMessage('Download failed');
    end);
end;

// cancellation
var
  Cancel: ICancellation;
begin
  Cancel := TTask.CurrentTask.Cancellation;
  while not Cancel.IsCancelled do
  begin
    DoChunk;
    Sleep(100);
  end;
end;
16

Netzwerkprogrammierung mit Indy

TCP-Client & -Server (Indy)

Indy (Internet Direct) ist Delphis gebündelte Netzwerkbibliothek. TIdTCPClient verbindet sich mit Servern — WriteLn/ReadLn für zeilenbasierte Protokolle, Write/Read für Binär. ConnectTimeout verhindert Hängen. TIdTCPServer lauscht auf Verbindungen — OnExecute läuft in einem Thread pro Client (AContext repräsentiert jede Verbindung). Indy verwendet blockierende Sockets (einfacheres Modell — keine Callbacks), sodass Server-Handler in Worker-Threads laufen. Behandeln Sie Trennungen immer graceful. Für hochleistungs-Server ziehen Sie ICS (overlapped I/O) oder Synapse in Betracht. Indy-Komponenten sind nicht-visuell — auf ein Formular ziehen oder im Code erstellen. Setzen Sie Active := True, um zu lauschen. DefaultPort setzt den lauschenden Port.

delphi
uses IdTCPClient, IdTCPServer, IdContext;

// TCP Client
var
  Client: TIdTCPClient;
  Response: string;
begin
  Client := TIdTCPClient.Create(nil);
  try
    Client.Host := 'example.com';
    Client.Port := 8080;
    Client.ConnectTimeout := 5000;
    Client.Connect;
    try
      Client.IOHandler.WriteLn('Hello Server');
      Response := Client.IOHandler.ReadLn;
      ShowMessage('Server: ' + Response);
    finally
      Client.Disconnect;
    end;
  finally
    Client.Free;
  end;
end;

// TCP Server
type
  TForm1 = class(TForm)
    IdTCPServer1: TIdTCPServer;
    procedure FormCreate(Sender: TObject);
    procedure ServerExecute(AContext: TIdContext);
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  IdTCPServer1.DefaultPort := 8080;
  IdTCPServer1.OnExecute := ServerExecute;
  IdTCPServer1.Active := True;
end;

procedure TForm1.ServerExecute(AContext: TIdContext);
var
  Msg: string;
begin
  Msg := AContext.Connection.IOHandler.ReadLn;
  AContext.Connection.IOHandler.WriteLn('Echo: ' + Msg);
  if Msg = 'quit' then
    AContext.Connection.Disconnect;
end;

HTTP-Client (TIdHTTP)

TIdHTTP ist Indys HTTP-Client — unterstützt GET, POST, PUT, DELETE, Header, Cookies und SSL/TLS. Für HTTPS hängen Sie TIdSSLIOHandlerSocketOpenSSL an (erfordert OpenSSL-DLLs: libeay32/ssleay32 oder libcrypto/libssl). Request.ContentType und CustomHeaders setzen Anfrage-Metadaten. POST akzeptiert einen String-Body (für JSON/APIs) oder TStrings (für Formulardaten). EIdHTTPProtocolException fängt HTTP-Fehler (404, 500 usw.) mit ErrorCode und ErrorMessage ab. Für moderne REST-Clients ziehen Sie TRESTClient (eingebaut, keine OpenSSL-Abhängigkeit) oder TNetHTTPClient (leichter) in Betracht. Geben Sie HTTP und SSL-Handler immer in finally-Blöcken frei. Setzen Sie Http.HandleRedirects := True, um 301/302-Weiterleitungen automatisch zu folgen.

delphi
uses IdHTTP, IdSSLOpenSSL, System.JSON;

var
  Http: TIdHTTP;
  SSL: TIdSSLIOHandlerSocketOpenSSL;
  Response: string;
  JSON: TJSONObject;
  Params: TStringList;
begin
  Http := TIdHTTP.Create(nil);
  SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  try
    Http.IOHandler := SSL;
    SSL.SSLOptions.Method := sslvTLSv1_2;
    Http.Request.ContentType := 'application/json';
    Http.Request.CustomHeaders.AddValue('Authorization', 'Bearer token123');

    // GET request
    Response := Http.Get('https://api.example.com/users');
    ShowMessage(Response);

    // POST with JSON body
    JSON := TJSONObject.Create;
    try
      JSON.AddPair('name', 'Alice');
      JSON.AddPair('age', 30);
      Response := Http.Post('https://api.example.com/users', JSON.ToJSON);
    finally
      JSON.Free;
    end;

    // POST form data
    Params := TStringList.Create;
    try
      Params.Add('username=alice');
      Params.Add('password=secret');
      Response := Http.Post('https://api.example.com/login', Params);
    finally
      Params.Free;
    end;

    // handle errors
    try
      Http.Get('https://api.example.com/missing');
    except
      on E: EIdHTTPProtocolException do
        ShowMessage('HTTP ' + IntToStr(E.ErrorCode) + ': ' + E.ErrorMessage);
    end;
  finally
    SSL.Free;
    Http.Free;
  end;
end;

SMTP-E-Mail (TIdSMTP)

TIdSMTP sendet E-Mail über SMTP-Server. TIdMessage repräsentiert die E-Mail (From, Recipients, Subject, Body). Für Gmail/Office365 verwenden Sie TLS (Port 587, utUseExplicitTLS) oder SSL (Port 465, utUseImplicitTLS). Gmail erfordert ein 'App-Passwort' (nicht Ihr reguläres Passwort) mit aktiviertem 2FA. TIdAttachmentFile fügt Dateianhänge hinzu. Für HTML-E-Mails setzen Sie ContentType := 'text/html'. Für Multipart (HTML + Klartext + Anhänge) verwenden Sie TIdMessageBuilderHTML. Häufige Ports: 25 (unverschlüsselt/Relay), 465 (SSL), 587 (STARTTLS). Umschließen Sie Connect/Send immer mit try/finally, um Disconnect sicherzustellen. Für den Empfang von E-Mail verwenden Sie TIdPOP3 oder TIdIMAP4.

delphi
uses IdSMTP, IdMessage, IdSSLOpenSSL, IdExplicitTLSClientServerBase;

var
  SMTP: TIdSMTP;
  Msg: TIdMessage;
  SSL: TIdSSLIOHandlerSocketOpenSSL;
begin
  SMTP := TIdSMTP.Create(nil);
  Msg := TIdMessage.Create(nil);
  SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  try
    // SMTP config (Gmail example)
    SMTP.Host := 'smtp.gmail.com';
    SMTP.Port := 587;
    SMTP.UseTLS := utUseExplicitTLS;
    SMTP.IOHandler := SSL;
    SSL.SSLOptions.Method := sslvTLSv1_2;
    SMTP.Username := '[email protected]';
    SMTP.Password := 'app-password';

    // message
    Msg.From.Address := '[email protected]';
    Msg.From.Name := 'My App';
    Msg.Recipients.Add.Address := '[email protected]';
    Msg.Subject := 'Test from Delphi';
    Msg.Body.Text := 'Hello,' + sLineBreak + 'This is a test email.';

    // attachment
    var Attachment := TIdAttachmentFile.Create(Msg.MessageParts,
      'C:\report.pdf');

    // HTML body
    Msg.ContentType := 'text/html';
    Msg.Body.Text := '<h1>Hello</h1><p>HTML email from Delphi</p>';

    // connect and send
    SMTP.Connect;
    try
      SMTP.Send(Msg);
      ShowMessage('Email sent!');
    finally
      SMTP.Disconnect;
    end;
  finally
    SSL.Free;
    Msg.Free;
    SMTP.Free;
  end;
end;

UDP & Raw Sockets

UDP ist verbindungslos — kein Handshake, keine garantierte Zustellung, aber schneller als TCP. TIdUDPClient.Send feuert Datagramme; ReceiveString wartet auf Antworten mit Timeout. BroadcastEnabled sendet an 255.255.255.255 (alle Geräte im LAN) — nützlich für Service-Discovery. TIdUDPServer.OnUDPRead empfängt Datagramme; ABinding.PeerIP/PeerPort identifizieren den Sender. UDP ist ideal für: DNS, SNMP, Spielzustandsaktualisierungen, Streaming-Medien und Discovery-Protokolle. Für Zuverlässigkeit über UDP implementieren Sie ACK/Retry auf Anwendungsebene. TIdBytes ist Indys Byte-Array-Typ — verwenden Sie BytesToString/ToBytes für Konvertierung. Für rohe Socket-Kontrolle (rohe IP-Pakete, benutzerdefinierte Protokolle) verwenden Sie die WinSock2-Unit oder die Synapse-Bibliothek.

delphi
uses IdUDPClient, IdUDPServer, IdSocketHandle;

// UDP Client (connectionless, fire-and-forget)
var
  UDP: TIdUDPClient;
begin
  UDP := TIdUDPClient.Create(nil);
  try
    UDP.Host := '255.255.255.255';  // broadcast
    UDP.Port := 9999;
    UDP.BroadcastEnabled := True;
    UDP.Send('DISCOVER');

    // receive response
    var Response: string;
    UDP.ReceiveString(Response, 1000);  // timeout 1s
    ShowMessage(Response);
  finally
    UDP.Free;
  end;
end;

// UDP Server
type
  TForm1 = class(TForm)
    IdUDPServer1: TIdUDPServer;
    procedure FormCreate(Sender: TObject);
    procedure UDPRead(AThread: TIdUDPListenerThread;
      const AData: TIdBytes; ABinding: TIdSocketHandle);
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  IdUDPServer1.DefaultPort := 9999;
  IdUDPServer1.OnUDPRead := UDPRead;
  IdUDPServer1.Active := True;
end;

procedure TForm1.UDPRead(AThread: TIdUDPListenerThread;
  const AData: TIdBytes; ABinding: TIdSocketHandle);
var
  Msg: string;
begin
  Msg := BytesToString(AData);
  // reply to sender
  ABinding.SendTo(ABinding.PeerIP, ABinding.PeerPort,
    ToBytes('ACK: ' + Msg));
end;

// raw socket with TIdIOHandlerSocket
// for low-level protocols, use WinSock2 unit directly

FTP & REST-Client

TIdFTP bietet FTP-Client-Funktionalität — Connect, List, Put (Upload), Get (Download), MakeDir, ChangeDir. Passiver Modus (Passive := True) funktioniert durch NAT/Firewalls. UseTLS sichert FTP (FTPS). Für SFTP (SSH-basiert) verwenden Sie eine Drittanbieter-Bibliothek (libssh2, SecureBlackbox). TRESTClient/TRESTRequest/TRESTResponse sind eingebaute REST-Komponenten (keine OpenSSL-Abhängigkeit) — ideal für modernen API-Konsum. Resource verwendet {param}-Platzhalter, die durch AddUrlSegment gefüllt werden. Execute sendet die Anfrage; RESTResponse.Content hält den Body; JSONValue parst JSON automatisch. REST-Komponenten unterstützen OAuth2, Basic Auth und benutzerdefinierte Authentifikatoren. Für hochleistungs-REST ziehen Sie TNetHTTPClient (leichter) oder Indys TIdHTTP für maximale Kontrolle in Betracht.

delphi
uses IdFTP, IdFTPCommon, IdExplicitTLSClientServerBase;

// FTP client
var
  FTP: TIdFTP;
begin
  FTP := TIdFTP.Create(nil);
  try
    FTP.Host := 'ftp.example.com';
    FTP.Username := 'user';
    FTP.Password := 'pass';
    FTP.Passive := True;            // NAT-friendly mode
    FTP.UseTLS := utUseExplicitTLS;
    FTP.Connect;
    try
      // list directory
      var Listing: TStringList := TStringList.Create;
      try
        FTP.List(Listing);
        for var S in Listing do
          ShowMessage(S);
      finally
        Listing.Free;
      end;

      // change directory
      FTP.ChangeDir('/uploads');

      // upload/download
      FTP.Put('C:\local.txt', 'remote.txt');
      FTP.Get('remote.txt', 'C:\downloaded.txt');

      // create/remove directory
      FTP.MakeDir('newfolder');
      FTP.RemoveDir('oldfolder');
    finally
      FTP.Disconnect;
    end;
  finally
    FTP.Free;
  end;
end;

// REST Client (built-in, no Indy needed)
uses REST.Client, REST.Types;

var
  RESTClient: TRESTClient;
  RESTRequest: TRESTRequest;
  RESTResponse: TRESTResponse;
begin
  RESTClient := TRESTClient.Create('https://api.example.com');
  RESTRequest := TRESTRequest.Create(RESTClient);
  RESTResponse := TRESTResponse.Create(nil);
  try
    RESTRequest.Resource := 'users/{id}';
    RESTRequest.Method := TRESTRequestMethod.rmGET;
    RESTRequest.Params.AddUrlSegment('id', '42');
    RESTRequest.Params.AddItem('fields', 'name,email', pkGETorPOST);

    RESTRequest.Execute;
    ShowMessage(RESTResponse.Content);     // JSON response

    // access JSON fields directly
    ShowMessage(RESTResponse.JSONValue.GetValue<string>('name'));
  finally
    RESTResponse.Free;
    RESTRequest.Free;
    RESTClient.Free;
  end;
end;
17

DLL & BPL-Pakete

DLLs erstellen & verwenden

DLLs (Dynamic Link Libraries) teilen Code über Anwendungen. Verwenden Sie das 'library'-Schlüsselwort (nicht 'program'), um eine DLL zu erstellen. 'exports' listet Funktionen auf, die für externe Aufrufer verfügbar sind. stdcall ist die Standard-Windows-Aufrufkonvention (C/C++, VB, C#-kompatibel). Statischer Import (external) verlinkt zur Kompilierzeit — DLL muss zur Laufzeit existieren. Dynamisches Laden (LoadLibrary/GetProcAddress) lädt zur Laufzeit — ermöglicht Plugins und optionale Features. FreeLibrary entlädt die DLL. PChar (PWideChar) ist der Standard-String-Typ für DLL-Exporte (Shared Memory, keine Delphi-spezifischen Typen). Exportieren Sie NIEMALS Delphi-Strings, -Objekte oder -Schnittstellen direkt — sie sind Delphi-intern. Verwenden Sie die ShareMem-Unit für Delphi-zu-Delphi-String-Sharing (erfordert BorlndMM.dll).

delphi
// --- MyLib.dpr (DLL project) ---
library MyLib;

uses
  System.SysUtils, System.Classes;

// exported function (stdcall for compatibility)
function Add(A, B: Integer): Integer; stdcall;
begin
  Result := A + B;
end;

// exported procedure
procedure ShowMessage(const Msg: PChar); stdcall;
begin
  WriteLn(Msg);
end;

// export table
exports
  Add name 'Add',
  ShowMessage name 'ShowMessage';

begin
end.

// --- MainApp.dpr (consumer) ---
// static import
function Add(A, B: Integer): Integer; stdcall; external 'MyLib.dll';
procedure ShowMsg(const Msg: PChar); stdcall; external 'MyLib.dll';

begin
  ShowMessage(IntToStr(Add(3, 4)));  // 7
end;

// dynamic loading (load at runtime)
var
  LibHandle: THandle;
  AddFunc: function(A, B: Integer): Integer; stdcall;
begin
  LibHandle := LoadLibrary('MyLib.dll');
  if LibHandle <> 0 then
  try
    @AddFunc := GetProcAddress(LibHandle, 'Add');
    if Assigned(@AddFunc) then
      ShowMessage(IntToStr(AddFunc(10, 20)));
  finally
    FreeLibrary(LibHandle);
  end;
end;

Objekte über Schnittstellen teilen

Das Teilen von Objekten über DLL-Grenzen ist knifflig — Delphi-Klassen können nicht direkt exportiert werden (verschiedene Speichermanager, verschiedene RTTI). Die Lösung: Verwenden Sie Schnittstellen mit GUIDs. Die DLL exportiert eine Factory-Funktion (CreatePlugin), die ein IPlugin zurückgibt. Die Host-App definiert dieselbe Schnittstelle (gleiche GUID!) und ruft die Factory auf. Schnittstellen-Referenzzählung handhabt Bereinigung automatisch. Verwenden Sie PChar für Strings (nicht Delphi-String), um Speichermanager-Konflikte zu vermeiden. Dies ist das Plugin-Architektur-Muster — DLLs dynamisch laden, Plugins über Factory erstellen, über Schnittstellen kommunizieren. Für vollständige Plugin-Systeme ziehen Sie das Plugin-Framework in Delphi in Betracht oder verwenden Sie Packages (BPL), die die RTL teilen und direkte Klassenfreigabe erlauben.

delphi
// --- PluginDLL.dpr ---
library PluginDLL;

type
  IPlugin = interface
    ['{12345678-1234-1234-1234-123456789012}']
    function GetName: PChar; stdcall;
    function Execute(const Input: PChar): PChar; stdcall;
    procedure Free; stdcall;
  end;

  TMyPlugin = class(TInterfacedObject, IPlugin)
  public
    function GetName: PChar; stdcall;
    function Execute(const Input: PChar): PChar; stdcall;
    procedure Free; stdcall;
  end;

function TMyPlugin.GetName: PChar;
begin
  Result := 'My Plugin v1.0';
end;

function TMyPlugin.Execute(const Input: PChar): PChar;
begin
  Result := PChar('Processed: ' + Input);
end;

// factory function - creates and returns the plugin
function CreatePlugin: IPlugin; stdcall;
begin
  Result := TMyPlugin.Create;
end;

exports CreatePlugin;

// --- HostApp.dpr ---
type
  IPlugin = interface
    ['{12345678-1234-1234-1234-123456789012}']
    function GetName: PChar; stdcall;
    function Execute(const Input: PChar): PChar; stdcall;
    procedure Free; stdcall;
  end;

var
  CreatePlugin: function: IPlugin; stdcall;
  Plugin: IPlugin;
  Handle: THandle;
begin
  Handle := LoadLibrary('PluginDLL.dll');
  if Handle <> 0 then
  try
    @CreatePlugin := GetProcAddress(Handle, 'CreatePlugin');
    if Assigned(@CreatePlugin) then
    begin
      Plugin := CreatePlugin;
      ShowMessage(Plugin.GetName);
      ShowMessage(Plugin.Execute('test'));
    end;
  finally
    FreeLibrary(Handle);
  end;
end;

BPL-Pakete (Borland Package Library)

BPLs (Borland Package Libraries) sind Delphi-spezifische Shared Libraries — sie teilen die Delphi-RTL, was direkte Klassen-/Objektfreigabe ermöglicht (im Gegensatz zu DLLs). Erstellen Sie mit dem 'package'-Schlüsselwort. Runtime-Packages reduzieren die EXE-Größe (geteilter Code in .bpl-Dateien) und ermöglichen hot-swappable Module. LoadPackage/UnloadPackage laden BPLs dynamisch — GetClass findet registrierte Klassen nach Namen. RegisterClass/UnRegisterClass machen Klassen entdeckbar. BPLs erfordern, dass die Delphi-RTL-BPLs (rtl.bpl, vcl.bpl) bereitgestellt werden. Verwenden Sie BPLs für: Plugin-Architekturen (Delphi-Typen direkt teilen), modulare Anwendungen (Features bei Bedarf laden) und Speicherreduzierung (geteilter Code einmal geladen). Für sprachübergreifendes Teilen verwenden Sie DLLs; für Delphi-only sind BPLs mächtiger.

delphi
// --- MyPackage.dpk (runtime package) ---
package MyPackage;

requires
  rtl,
  vcl;

contains
  MyUnit in 'MyUnit.pas',
  MyForm in 'MyForm.pas' {Form1};

// compile: dcc32 -B MyPackage.dpk
// produces MyPackage.bpl (shared runtime package)

// --- Using the package ---
// Option 1: link statically (compile-time reference)
// Project → Options → Packages → Runtime packages → add MyPackage.bpl

// Option 2: load dynamically with LoadPackage
var
  PackageModule: THandle;
  FormClass: TPersistentClass;
begin
  PackageModule := LoadPackage('MyPackage.bpl');
  try
    // register and use forms/classes from the package
    FormClass := GetClass('TForm1');
    if FormClass <> nil then
      with TFormClass(FormClass).Create(Application) do
        try
          ShowModal;
        finally
          Free;
        end;
  finally
    UnloadPackage(PackageModule);
  end;
end;

// RegisterClass in the package's unit:
unit MyForm;
interface
uses Vcl.Forms;
type
  TForm1 = class(TForm)
  end;
implementation
initialization
  RegisterClass(TForm1);    // make class discoverable
finalization
  UnRegisterClass(TForm1);
end.

Speicherverwaltung über Grenzen hinweg

Die #1-DLL-Falle: Speicher in einem Modul freigeben, das in einem anderen zugewiesen wurde. Jedes Modul hat seinen eigenen Speichermanager — das Mischen verursacht Heap-Korruption und Abstürze. Lösungen: (1) ShareMem — teilt BorlndMM.dll, erfordert aber Bereitstellung dieser DLL. (2) Caller-allocates-Muster — Aufrufer stellt Puffer bereit, DLL füllt ihn (am sichersten, sprachagnostisch). (3) SimpleShareMem/FastMM — moderner Shared-Memory-Manager (FastMM ist Standard seit Delphi 2006). (4) Callback-basiertes Freigeben — DLL stellt eine Free-Funktion bereit. Für PChar-Rückgaben verwenden Sie StrNew/StrDispose (Windows-API, geteilt). Für Produktion Delphi-zu-Delphi verwenden Sie BPLs (geteilte RTL) oder SimpleShareMem. Für sprachübergreifend verwenden Sie immer das Caller-allocates-Muster. Geben Sie niemals Delphi-String-/Objekt-/Schnittstellen-Typen über DLL-Grenzen ohne Shared-Memory-Manager weiter.

delphi
// PROBLEM: different memory managers in EXE and DLL
// → crashes when freeing memory allocated in another module

// Solution 1: ShareMem (Delphi-to-Delphi only)
// First unit in both EXE and DLL .dpr file:
uses
  ShareMem;  // uses BorlndMM.dll as shared memory manager

// Solution 2: Caller allocates, caller frees (safest)
// DLL fills a buffer provided by the caller
procedure GetData(Buffer: PChar; var BufSize: Integer); stdcall;
var
  Data: string;
begin
  Data := 'Hello from DLL';
  BufSize := Length(Data) + 1;
  if Buffer <> nil then
    StrLCopy(Buffer, PChar(Data), BufSize);
end;

// caller:
var
  Size: Integer;
  Buffer: PChar;
begin
  GetData(nil, Size);              // query size
  GetMem(Buffer, Size);            // allocate
  try
    GetData(Buffer, Size);         // fill
    ShowMessage(Buffer);
  finally
    FreeMem(Buffer);               // caller frees
  end;
end;

// Solution 3: Use SafeCall / COM-style allocation
// Solution 4: Use FastMM as shared manager (modern approach)
// Add SimpleShareMem unit (uses FastMM) to both projects

// Solution 5: Return only simple types / PChar with callback
type
  TFreeCallback = procedure(Ptr: Pointer); stdcall;
function CreateString(out S: PChar; FreeProc: TFreeCallback): Boolean; stdcall;
begin
  S := StrNew('Allocated in DLL');
  Result := True;
  // caller calls FreeProc(S) which calls StrDispose in the DLL
end;

Ressourcendateien & Einbettung

Ressourcendateien betten Binärdaten (Bilder, Icons, Sounds, Strings, Versionsinfo) in die EXE/DLL ein — keine externen Dateien nötig. Erstellen Sie ein .rc-Skript, kompilieren Sie mit brcc32 (oder lassen Sie die IDE automatisch kompilieren). {$R file.res} verlinkt es. TResourceStream liest RCDATA-Ressourcen als Stream. LoadIcon/LoadString verwenden Windows-API für spezifische Ressourcentypen. Ressourcen sind zur Laufzeit nur-Lese, aber halten alles in einer Datei (großartig für Bereitstellung). Häufige Verwendungen: Anwendungs-Icons, Splash-Screen-Bilder, Standard-Konfiguration, WAV-Sounds, Versionsinfo (Dateieigenschaften-Dialog), lokalisierte Strings. Für große Daten ziehen Sie Komprimierung vor dem Einbetten in Betracht. Ressourcen-IDs können Namen (Strings) oder Zahlen sein. RT_RCDATA ist der generische Binär-Ressourcentyp.

delphi
// --- Resource script (.rc file) ---
// MyResources.rc:
//   LOGO     RCDATA "logo.png"
//   ICON1    ICON    "app.ico"
//   VERSION  VERSIONINFO ...
//   WAVE1    WAVE    "sound.wav"
//   STR1     STRINGTABLE { "Hello" }

// compile: brcc32 MyResources.rc → MyResources.res
// or add .rc to project (auto-compiled)

// --- In .dpr ---
{$R MyResources.res}  // link resource

// --- Loading resources ---
uses System.Classes, Vcl.Graphics, Winapi.Windows;

// load RCDATA (binary data)
var
  Stream: TResourceStream;
begin
  Stream := TResourceStream.Create(HInstance, 'LOGO', RT_RCDATA);
  try
    Image1.Picture.LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

// load icon
var
  Icon: TIcon;
begin
  Icon := TIcon.Create;
  try
    Icon.Handle := LoadIcon(HInstance, 'ICON1');
    Image1.Picture.Icon.Assign(Icon);
  finally
    Icon.Free;
  end;
end;

// load string resource
var
  S: string;
  Buffer: array[0..255] of Char;
begin
  LoadString(HInstance, 1, Buffer, SizeOf(Buffer));
  S := Buffer;
end;

// embed a file as resource at compile time
// {$R 'data.bin' 'data.bin'}  // or use .rc
18

Debugging & Leistungsoptimierung

Debugger & Breakpoints

Delphis IDE-Debugger ist mächtig: Setzen Sie Breakpoints durch Klicken auf den Gutter. Bedingte Breakpoints unterbrechen nur, wenn ein Ausdruck wahr ist (z. B. i > 100). Log/Trace-Breakpoints protokollieren Nachrichten, ohne zu stoppen — großartig zum Überwachen von Schleifen. asm int 3 end erstellt einen harten Breakpoint im Code (CPU-Trap). OutputDebugString protokolliert im Ereignisprotokoll-Fenster (und DebugView-Tool). Assert prüft Bedingungen in Debug-Builds (mit {$C-} oder Assertionen aus in Release deaktiviert). DebugHook ist non-zero, wenn in der IDE ausgeführt. Das Call Stack-Fenster verfolgt die Aufrufkette; das Threads-Fenster inspiziert alle Threads; Local Variables zeigt den aktuellen Gültigkeitsbereich. Aktivieren Sie 'Use Debug DCUs', um in RTL/VCL-Quellcode zu treten.

delphi
// Conditional breakpoints (set in IDE):
//   Break when expression is true
//   e.g., (i > 100) and (List.Count > 0)

// Log breakpoints (no break, just log):
//   Log message: "Iteration {i}, Count={List.Count}"

// Trace points / Action breakpoints:
//   Run macro or evaluate expression on hit

// Code-based breakpoints:
var
  I: Integer;
begin
  for I := 1 to 1000 do
  begin
    // break only when condition met
    if (I mod 100 = 0) and DebugHook <> 0 then
      asm int 3 end;            // hard breakpoint (CPU trap)

    // or use OutputDebugString for logging
    OutputDebugString(PChar('Processing ' + IntToStr(I)));
  end;
end;

// Assert (only in debug builds)
Assert(List.Count > 0, 'List must not be empty');

// DebugHook: 0 = release, 1 = IDE, 2 = IDE step-over
if DebugHook <> 0 then
  ShowMessage('Running in debugger');

// Watch and Evaluate expressions in IDE:
//   List.Count
//   List[0].Name
//   TMyObject(Obj).PrivateField  (with "Use Debug DCUs")

// Call Stack window shows the call chain
// Threads window shows all active threads
// Local Variables shows current scope variables

Ausnahmebehandlung & Stapelüberwachungen

Delphi-Ausnahmen: try/except fängt Fehler, try/finally garantiert Bereinigung. Ausnahmeklassen bilden eine Hierarchie: Exception → EDivByZero, EAccessViolation, EListError, EAbort (still) usw. 'on E: ExceptionType do' fängt spezifische Typen; das Basis 'on E: Exception do' fängt alle. 'raise;' löst die aktuelle Ausnahme erneut aus (bewahrt Stapelüberwachung). EAbort (oder Abort-Prozedur) löst eine stille Ausnahme aus (kein Dialog). TApplicationEvents.OnException ist der globale Handler — fängt unbehandelte Ausnahmen. Für Stapelüberwachungen verwenden Sie JCL (JclDebug) oder MadExcept/ExceptionHunter — sie erfassen Call-Stacks, Register-Dumps und senden sogar Absturzberichte per E-Mail. Protokollieren Sie Ausnahmen immer für Post-Mortem-Debugging. Schlucken Sie Ausnahmen in Produktion niemals stillschweigend.

delphi
uses
  System.SysUtils, System.Diagnostics;

// structured exception handling
try
  RiskyOperation;
except
  on E: EDivByZero do
    ShowMessage('Division error: ' + E.Message);
  on E: EAccessViolation do
    ShowMessage('Access violation at ' + E.Message);
  on E: Exception do
  begin
    ShowMessage('Unexpected: ' + E.ClassName + ': ' + E.Message);
    raise;                         // re-raise
  end;
end;

// finally (always executes)
try
  AcquireResource;
  UseResource;
finally
  ReleaseResource;                 // always runs
end;

// nested try/except/finally
try
  try
    RiskyCode;
  except
    on E: Exception do
    begin
      LogError(E);
      raise EAbort.Create('');     // suppress display
    end;
  end;
finally
  Cleanup;
end;

// global exception handler
procedure TForm1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
begin
  LogError(Format('%s: %s', [E.ClassName, E.Message]));
  if not (E is EAbort) then
    ShowMessage('Error: ' + E.Message);
end;

// get stack trace (with JCL or MadExcept)
// JclDebug: JclCreateStackInfo, JclLastExceptStackList

Profiling & Leistung

TStopwatch ist der hochpräzise Timer (verwendet QueryPerformanceCounter). Benchmarken Sie immer vor dem Optimieren — raten Sie nicht. ReportMemoryLeaksOnShutdown := True fängt Lecks beim Programmende (Debug-Builds). Häufige Delphi-Leistungsfallen: (1) String-Verkettung in Schleifen erstellt Kopien — verwenden Sie TStringBuilder oder pre-allokieren Sie. (2) SetLength in einer Schleife re-allokiert — setzen Sie Größe einmal. (3) Strings/Arrays als Wert zu übergeben kopiert sie — verwenden Sie 'const' für Nur-Lese-Parameter. (4) TStringList.Sorted + Find ist O(log n); unsortiertes IndexOf ist O(n). (5) TList<T>.Add ist amortisiert O(1), aber Insert am Anfang ist O(n). Für tiefes Profiling verwenden Sie Sampling Profiler (kostenlos), AQTime oder GpProfile — sie identifizieren Hotspots ohne Code-Änderungen. Optimieren Sie die 20% des Codes, die 80% der Zeit benötigen.

delphi
uses System.Diagnostics;

// TStopwatch - precise timing
var
  SW: TStopwatch;
  Elapsed: Int64;
begin
  SW := TStopwatch.StartNew;
  try
    ExpensiveOperation;
  finally
    SW.Stop;
    ShowMessage(Format('Elapsed: %d ms', [SW.ElapsedMilliseconds]));
  end;
end;

// benchmark comparison
function Benchmark(const Name: string; const Action: TProc): Int64;
var
  SW: TStopwatch;
  I: Integer;
begin
  SW := TStopwatch.StartNew;
  for I := 1 to 1000 do
    Action;
  SW.Stop;
  WriteLn(Format('%s: %d ms', [Name, SW.ElapsedMilliseconds]));
  Result := SW.ElapsedMilliseconds;
end;

// memory usage
var
  Mem: TMemoryManagerState;
begin
  GetMemoryManagerState(Mem);
  ShowMessage(Format('Allocated: %d bytes', [Mem.TotalAllocated]));

  // report memory leaks on shutdown
  ReportMemoryLeaksOnShutdown := True;  // shows leak dialog on exit
end;

// common optimizations:
// 1. Use TStringBuilder for heavy string concatenation
var SB := TStringBuilder.Create;
try
  for var I := 1 to 10000 do
    SB.Append('Line ').Append(I).AppendLine;
  Result := SB.ToString;
finally
  SB.Free;
end;

// 2. SetLength once, not in a loop
SetLength(Result, Count);  // pre-allocate
for I := 0 to Count - 1 do
  Result[I] := Compute(I);

// 3. Use const for strings/arrays (avoids copy)
procedure Process(const Data: string);  // const = no copy

Speicherverwaltung & Lecks

Speicherverwaltung ist Delphis größte Quelle von Bugs. Regel #1: Jedes Create muss ein passendes Free haben. Verwenden Sie try/finally religiös. Für automatische Verwaltung verwenden Sie Schnittstellen (TInterfacedObject + Referenzzählung) — kein Free nötig. TObjectList<T> mit OwnsObjects := True gibt enthaltene Objekte automatisch frei. ReportMemoryLeaksOnShutdown := True zeigt einen Dialog mit geleakten Objekten beim Beenden (nur Debug). FastMM (der Standard-Speichermanager) in FullDebugMode protokolliert Lecks mit Allokations-Stapelüberwachungen in eine Datei — unerlässlich für das Aufspüren von Lecks. Häufige Leak-Muster: fehlendes try/finally, nicht entfernte Event-Handler, zirkuläre Referenzen (mit [Weak] beheben), nicht freigegebene Threads, in finalization nicht freigegebene globale Objekte. Der finalization-Abschnitt der Unit läuft beim Herunterfahren — verwenden Sie ihn für globale Bereinigung.

delphi
// Rule: every Create needs a Free (or use interfaces)

// Pattern 1: try/finally
var
  Obj: TMyObject;
begin
  Obj := TMyObject.Create;
  try
    Obj.DoWork;
  finally
    Obj.Free;    // always freed
  end;
end;

// Pattern 2: interface reference counting (automatic)
var
  Obj: IMyInterface;
begin
  Obj := TMyObject.Create;  // TInterfacedObject
  Obj.DoWork;
  // freed automatically when Obj goes out of scope
end;

// Pattern 3: TObjectList (owns children)
var
  List: TObjectList<TPerson>;
begin
  List := TObjectList<TPerson>.Create(True);  // OwnsObjects
  try
    List.Add(TPerson.Create('Alice'));
    List.Add(TPerson.Create('Bob'));
    // freeing List frees all TPerson objects
  finally
    List.Free;
  end;
end;

// Detecting leaks
// 1. ReportMemoryLeaksOnShutdown := True;
// 2. FastMM (default since D2006) with FullDebugMode
//    → logs leaks with stack traces to file
// 3. Set breakpoint on System._DebugIntfMemLeak (FastMM)

// Common leak causes:
// - Create without Free (missing try/finally)
// - Event handler assigned but never removed
// - Circular references (use [Weak])
// - TThread not freed (FreeOnTerminate := True)
// - Global objects not freed in finalization

// finalization section for globals
var
  GlobalCache: TDictionary<string, TObject>;
initialization
  GlobalCache := TDictionary<string, TObject>.Create;
finalization
  GlobalCache.Free;    // cleanup on shutdown

Codequalität & Tests

DUnitX ist das moderne Unit-Test-Framework (ersetzt DUnit). [TestFixture] markiert Test-Klassen, [Test] markiert Test-Methoden, [Setup]/[TearDown] laufen vor/nach jedem Test. [TestCase] parametrisiert Tests mit Inline-Daten. Assert.AreEqual/IsTrue/WillRaise überprüfen Ergebnisse. Test-Driven Development (TDD): Schreiben Sie zuerst Tests, dann Code. Tests fangen Regressionen und dokumentieren erwartetes Verhalten. Delphi Mocks (oder Spring4D Mocking) erstellt Mock-Objekte aus Schnittstellen — Setup.Expect definiert Erwartungen, VerifyAll prüft, ob sie erfüllt wurden. Mocking ist unerlässlich für das Isolieren von Units (Datenbank, Netzwerk, Dateisystem mocken). Streben Sie hohe Abdeckung der Geschäftslogik an. Führen Sie Tests in CI (Continuous Integration) aus, um Regressionen früh zu erkennen. Integrationstests überprüfen, dass Komponenten zusammenarbeiten; Unit-Tests überprüfen einzelne Units isoliert.

delphi
// DUnitX - unit testing framework
uses DUnitX.TestFramework;

type
  [TestFixture]
  TCalculatorTests = class
  public
    [Setup]
    procedure Setup;

    [TearDown]
    procedure TearDown;

    [Test]
    procedure TestAdd;

    [Test]
    [TestCase('A', '1,2,3')]
    [TestCase('B', '10,20,30')]
    procedure TestAddParam(A, B, Expected: Integer);

    [Test]
    procedure TestDivideByZero;
  end;

procedure TCalculatorTests.TestAdd;
var
  Calc: TCalculator;
begin
  Calc := TCalculator.Create;
  try
    Assert.AreEqual(5, Calc.Add(2, 3));
    Assert.AreNotEqual(6, Calc.Add(2, 3));
    Assert.IsTrue(Calc.Add(0, 0) = 0);
  finally
    Calc.Free;
  end;
end;

procedure TCalculatorTests.TestDivideByZero;
var
  Calc: TCalculator;
begin
  Calc := TCalculator.Create;
  try
    Assert.WillRaise(
      procedure
      begin
        Calc.Divide(10, 0);
      end, EDivByZero);
  finally
    Calc.Free;
  end;
end;

// mock with interfaces
type
  [Mock]
  ILogger = interface
    ['{...}']
    procedure Log(const Msg: string);
  end;

// Delphi Mocks framework
var
  MockLogger: TMock<ILogger>;
begin
  MockLogger := TMock<ILogger>.Create;
  MockLogger.Setup.Expect.Once.When.Log('test');
  // ... use MockLogger.Object ...
  MockLogger.VerifyAll;  // asserts Log was called
end;
19

Generics & Collections

Generische Klassendeklaration

Generics lassen Sie typsichere Container ohne Casts schreiben. Deklarieren Sie mit <T> nach dem Typnamen. Der Compiler generiert eine spezialisierte Version pro verwendetem Typ. Verwenden Sie TArray<T> anstelle von array of für dynamische Arrays in generischen Typen.

delphi
type
  TStack<T> = class
  private
    FItems: TArray<T>;
    FCount: Integer;
  public
    procedure Push(const AValue: T);
    function Pop: T;
    function Peek: T;
    function Count: Integer;
  end;

procedure TStack<T>.Push(const AValue: T);
begin
  if FCount = Length(FItems) then
    SetLength(FItems, FCount * 2 + 4);
  FItems[FCount] := AValue;
  Inc(FCount);
end;

TDictionary-Verwendung

TDictionary<K,V> ist die generische Hash-Map. Add wirft bei doppelten Schlüsseln; OrAdd macht Upsert. TryGetValue gibt false (keine Ausnahme) bei fehlendem Schlüssel zurück. Geben Sie Wörterbücher immer frei — sie besitzen standardmäßig keine Objekte.

delphi
uses
  System.Generics.Collections;

var
  Dict: TDictionary<string, Integer>;
begin
  Dict := TDictionary<string, Integer>.Create;
  try
    Dict.Add('apple', 5);
    Dict.Add('banana', 3);
    Dict.OrAdd('apple', 10);  // add or replace

    if Dict.TryGetValue('apple', Value) then
      Writeln(Value);

    for var Pair in Dict do
      Writeln(Pair.Key, ': ', Pair.Value);
  finally
    Dict.Free;
  end;
end;

TList mit Vergleicher

TList<T>.Sort verwendet IComparer<T>. TComparer<T>.Construct wickelt eine anonyme Funktion in einen Vergleicher ein. BinarySearch erfordert, dass die Liste mit demselben Vergleicher sortiert ist. AddRange akzeptiert ein offenes Array oder eine andere Liste.

delphi
var
  List: TList<Integer>;
begin
  List := TList<Integer>.Create;
  try
    List.AddRange([5, 2, 8, 1, 9]);
    List.Sort;  // ascending

    // custom comparer (descending)
    List.Sort(TComparer<Integer>.Construct(
      function(const L, R: Integer): Integer
      begin
        Result := R - L;
      end));

    List.BinarySearch(8, Index);  // requires sorted list
  finally
    List.Free;
  end;
end;

Generische Constraints

Constraints schränken ein, welche Typen substituiert werden können: 'class' (Referenztyp), 'record' (Werttyp), 'constructor' (parameterloser Konstruktor) oder eine spezifische Vorfahrklasse. Mehrere Constraints durch Kommas getrennt. Ohne 'constructor' können Sie T.Create nicht aufrufen.

delphi
type
  TRepository<T: class, constructor> = class
  public
    function CreateInstance: T;
    procedure Save(const AEntity: T);
  end;

function TRepository<T>.CreateInstance: T;
begin
  Result := T.Create;  // requires 'constructor' constraint
end;

// multiple constraints: class, constructor, specific base
type
  TControlFactory<T: TControl, constructor> = class ... end;

Objektbesitz mit TObjectDictionary

TObjectDictionary<K,V> erweitert TDictionary mit Besitz. Übergeben Sie [doOwnsValues], [doOwnsKeys] oder beides. Bei Remove/Clear/Free werden besessene Objekte automatisch freigegeben — verhindert Speicherlecks in Objekt-Collections.

delphi
var
  Dict: TObjectDictionary<string, TButton>;
begin
  // owns values — frees them automatically
  Dict := TObjectDictionary<string, TButton>.Create([doOwnsValues]);
  try
    Dict.Add('btn1', TButton.Create(nil));
    Dict.Add('btn2', TButton.Create(nil));
    Dict.Remove('btn1');  // frees the TButton
  finally
    Dict.Free;  // frees remaining buttons
  end;
end;
20

Anonyme Methoden & Closures

Basis anonyme Methode

Anonyme Methoden sind Inline-Funktionsreferenzen. TFunc<...> ist für Funktionen, TProc<...> für Prozeduren. Sie erfassen Variablen aus dem umschließenden Gültigkeitsbereich (Closures). Variablen zuweisbar, als Parameter übergebbar.

delphi
var
  Adder: TFunc<Integer, Integer, Integer>;
begin
  Adder := function(A, B: Integer): Integer
    begin
      Result := A + B;
    end;

  Writeln(Adder(3, 4));  // 7
end;

Closure erfasst Variablen

Erfasste Variablen werden heap-zugewiesen und leben so lange wie die anonyme Methode. Jeder Aufruf von MakeMultiplier erfasst seinen eigenen Factor — Closures sind unabhängig. So funktionieren Factories und partielle Anwendung.

delphi
function MakeMultiplier(Factor: Integer): TFunc<Integer, Integer>;
begin
  Result := function(X: Integer): Integer
    begin
      Result := X * Factor;  // captures Factor
    end;
end;

var
  Double: TFunc<Integer, Integer>;
  Triple: TFunc<Integer, Integer>;
begin
  Double := MakeMultiplier(2);
  Triple := MakeMultiplier(3);
  Writeln(Double(10));  // 20
  Writeln(Triple(10));  // 30
end;

Higher-Order-Funktionen

'reference to' deklariert einen prozeduralen Typ, der mit anonymen Methoden kompatibel ist. Apply ist eine Higher-Order-Funktion — nimmt eine Funktion als Argument. Dies ermöglicht Map/Filter/Reduce-Muster. Verwenden Sie TArray<Integer> für dynamische Arrays.

delphi
type
  TIntFunc = reference to function(X: Integer): Integer;

function Apply(const F: TIntFunc; Values: array of Integer): TArray<Integer>;
var
  I: Integer;
begin
  SetLength(Result, Length(Values));
  for I := 0 to High(Values) do
    Result[I] := F(Values[I]);
end;

var
  Squared: TArray<Integer>;
begin
  Squared := Apply(function(X: Integer): Integer
    begin
      Result := X * X;
    end, [1, 2, 3, 4, 5]);
end;

Event-Handler mit Closures

Anonyme Methoden können traditionelle methodenbasierte Event-Handler ersetzen und Kontext ohne Felder erfassen. Nützlich für einmalige Handler und Reduzierung von Boilerplate. Das erfasste Caption bleibt am Leben mit der Closure-Referenz, die von OnClick gehalten wird.

delphi
procedure SetupButton(Button: TButton; const Caption: string);
begin
  Button.Caption := Caption;
  Button.OnClick := procedure(Sender: TObject)
    begin
      ShowMessage(Caption + ' clicked!');  // captures Caption
    end;
end;

// instead of:
//   procedure TForm1.Button1Click(Sender: TObject);
//   begin
//     ShowMessage('Button1 clicked!');
//   end;

TThread mit anonym

CreateAnonymousThread wickelt eine Closure in einen Thread — Fire-and-Forget-Hintergrundarbeit. Verwenden Sie TThread.Queue (oder Synchronize), um UI-Aktualisierungen an den Haupt-Thread zu marshallen. Greifen Sie niemals direkt von einem Worker-Thread auf UI-Steuerelemente zu.

delphi
TThread.CreateAnonymousThread(
  procedure
  var
    I: Integer;
  begin
    for I := 1 to 10 do
    begin
      TThread.Queue(nil,
        procedure
        begin
          Memo1.Lines.Add('Progress: ' + I.ToString);
        end);
      Sleep(100);
    end;
  end).Start;
21

Attribute & RTTI

Benutzerdefinierte Attributdeklaration

Attribute sind Klassen, die von TCustomAttribute erben. Anwenden mit [AttrName(...)] auf Typen, Felder, Methoden, Eigenschaften. Der Compiler bettet sie in RTTI ein. Konstruktorparameter werden Attributargumente.

delphi
type
  DisplayNameAttribute = class(TCustomAttribute)
  private
    FName: string;
  public
    constructor Create(const AName: string);
    property Name: string read FName;
  end;

  constructor DisplayNameAttribute.Create(const AName: string);
  begin
    FName := AName;
  end;

  [DisplayName('User Account')]
  TUser = class
    [DisplayName('Full Name')]
    FName: string;
  end;

Attribute über RTTI lesen

TRttiContext ist der Einstiegspunkt zu RTTI. GetType gibt TRttiType für eine Klasse zurück. GetAttributes gibt alle angewendeten Attribute zurück. Casten Sie auf Ihren Attributtyp, um Eigenschaften zu lesen. RTTI erfordert, dass die Klasse in einer Unit kompiliert mit {$M+} ist oder von TPersistent abgeleitet ist.

delphi
uses
  System.Rtti;

var
  Ctx: TRttiContext;
  RttiType: TRttiType;
  Attr: TCustomAttribute;
begin
  Ctx := TRttiContext.Create;
  try
    RttiType := Ctx.GetType(TUser);
    for Attr in RttiType.GetAttributes do
      if Attr is DisplayNameAttribute then
        Writeln(DisplayNameAttribute(Attr).Name);
  finally
    Ctx.Free;
  end;
end;

Feld- und Methoden-RTTI

GetFields gibt alle öffentlichen/veröffentlichten Felder zurück. SetValue/GetValue bieten dynamischen Feldzugriff nach Namen — nützlich für Serialisierer und ORMs. GetMethods gibt alle Methoden einschließlich geerbter zurück. RTTI ist langsamer als direkte Aufrufe.

delphi
var
  Ctx: TRttiContext;
  FieldType: TRttiField;
  Method: TRttiMethod;
  User: TUser;
begin
  Ctx := TRttiContext.Create;
  try
    User := TUser.Create;
    try
      for FieldType in Ctx.GetType(TUser).GetFields do
      begin
        Writeln(FieldType.Name, ': ', FieldType.FieldType.Name);
        FieldType.SetValue(User, 'Alice');  // set by RTTI
      end;
      Writeln(FieldType.GetValue(User).AsString);
    finally
      User.Free;
    end;
  finally
    Ctx.Free;
  end;
end;

Eigenschafts-RTTI und Aufruf

GetProperties gibt veröffentlichte Eigenschaften zurück. IsReadable/IsWritable prüfen Accessoren. GetValue/SetValue funktionieren auch auf Eigenschaften. TypeKind (tkInteger, tkString, tkClass usw.) lässt Sie jeden Typ angemessen behandeln. So arbeiten die meisten Delphi-Serialisierer.

delphi
var
  Ctx: TRttiContext;
  Prop: TRttiProperty;
  Instance: TMyClass;
begin
  Instance := TMyClass.Create;
  try
    for Prop in Ctx.GetType(TMyClass).GetProperties do
    begin
      if Prop.IsReadable then
        Writeln(Prop.Name, ' = ', Prop.GetValue(Instance).ToString);
      if Prop.IsWritable and (Prop.PropertyType.TypeKind = tkInteger) then
        Prop.SetValue(Instance, 42);
    end;
  finally
    Instance.Free;
  end;
end;

Methodenaufruf nach Name

GetMethod findet eine Methode nach Namen (case-sensitiv). Invoke ruft sie dynamisch mit TValue-Array-Argumenten auf. TValue ist ein variant-ähnlicher Wrapper für jeden Typ. Nützlich für Plugin-Systeme, Skripting und spätes Binden. Gibt TValue zurück — konvertieren Sie mit AsInteger, AsString usw.

delphi
var
  Ctx: TRttiContext;
  Method: TRttiMethod;
  Args: array of TValue;
  Result: TValue;
begin
  Method := Ctx.GetType(TMyClass).GetMethod('CalculateTotal');
  if Assigned(Method) then
  begin
    SetLength(Args, 2);
    Args[0] := 10;
    Args[1] := 20;
    Result := Method.Invoke(MyInstance, Args);
    Writeln(Result.AsInteger);
  end;
end;
22

Schnittstellen Vertieft

Schnittstellendeklaration und Implementierung

Schnittstellen definieren Verträge ohne Implementierung. GUIDs (optional, aber empfohlen) ermöglichen 'as'-Casts und Supports(). TInterfacedObject bietet Referenzzählung. Alle Schnittstellenmethoden müssen implementiert werden (kein 'abstract'-Ausweg). Eigenschaften in Schnittstellen benötigen Accessor-Methoden.

delphi
type
  IShape = interface
    ['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']
    function GetArea: Double;
    function GetPerimeter: Double;
    procedure Draw;
    property Color: TColor read FColor write SetColor;
  end;

  TCircle = class(TInterfacedObject, IShape)
  private
    FRadius: Double;
    FColor: TColor;
    procedure SetColor(Value: TColor);
  public
    constructor Create(ARadius: Double);
    function GetArea: Double;
    function GetPerimeter: Double;
    procedure Draw;
  end;

Referenzzählung und Speicher

Schnittstellenreferenzen sind referenzzählend. Wenn die letzte Schnittstellenreferenz den Gültigkeitsbereich verlässt, wird das Objekt freigegeben. Mischen Sie NIEMALS Objekt- und Schnittstellenreferenzen auf dieselbe Instanz — die Schnittstellen-Referenzzählung gibt sie frei, während der Objektzeiger noch darauf zeigt. Wählen Sie ein Besitzmodell.

delphi
var
  Shape: IShape;
begin
  Shape := TCircle.Create(5.0);  // refcount = 1
  // ... use Shape ...
end;  // refcount drops to 0, object freed automatically

// Mixing interface and object references — DANGER:
var
  Obj: TCircle;
begin
  Obj := TCircle.Create(5.0);
  Shape := Obj;       // refcount = 1
  Shape := nil;       // refcount = 0, Obj freed!
  Obj.GetArea;        // AV — dangling pointer
end;

Schnittstellenvererbung und mehrere Schnittstellen

Schnittstellen können von mehreren Eltern erben. Eine Klasse kann mehrere Schnittstellen implementieren. Method Resolution Clauses (method = interface.method) lösen Konflikte, wenn mehrere Schnittstellen dieselbe Methode deklarieren. Verwenden Sie 'as' oder Supports(), um zur Laufzeit nach einer Schnittstelle abzufragen.

delphi
type
  IReadable = interface
    function Read: string;
  end;

  IWritable = interface
    procedure Write(const S: string);
  end;

  IStream = interface(IReadable, IWritable)
    procedure Flush;
  end;

  TFileStream = class(TInterfacedObject, IStream, IReadable, IWritable)
    // must implement all methods from all interfaces
  end;

Supports und as-Casts

Supports() prüft, ob ein Objekt eine Schnittstelle implementiert — gibt Boolean zurück, optional die Schnittstelle. 'as'-Cast macht dasselbe, wirft aber EInvalidCast bei Fehlschlag. Supports() funktioniert sowohl auf Objekten als auch auf Schnittstellenreferenzen. Erfordert, dass die Schnittstelle eine GUID hat.

delphi
uses
  System.SysUtils, System.TypInfo;

var
  Obj: TObject;
  Shape: IShape;
begin
  Obj := TCircle.Create(5.0);
  try
    if Supports(Obj, IShape, Shape) then
      Writeln(Shape.GetArea:0:2);

    // 'as' cast — raises if not supported
    Shape := Obj as IShape;

    // type info
    if Supports(Obj, IShape) then
      Writeln('Obj supports IShape');
  finally
    Obj.Free;  // object reference — must free manually
  end;
end;

Dependency Injection Muster

Übergeben Sie Abhängigkeiten als Schnittstellen — ermöglicht Mocking, Austauschen von Implementierungen und Testbarkeit. Die Klasse hängt von der Abstraktion (ILogger) ab, nicht einem konkreten Typ. Dies ist die Grundlage von DI-Containern wie Spring4D. Schnittstellenbesitz bedeutet, dass der Logger so lange lebt, wie der Service die Referenz hält.

delphi
type
  ILogger = interface
    procedure Log(const Msg: string);
  end;

  TOrderService = class
  private
    FLogger: ILogger;
  public
    constructor Create(ALogger: ILogger);
    procedure ProcessOrder(OrderId: Integer);
  end;

constructor TOrderService.Create(ALogger: ILogger);
begin
  FLogger := ALogger;  // injected dependency
end;

procedure TOrderService.ProcessOrder(OrderId: Integer);
begin
  FLogger.Log('Processing order ' + OrderId.ToString);
end;
23

Speicherverwaltung Erweitert

Try-finally-Muster

Paaren Sie Allokation immer mit Free in try-finally. Verschachteln Sie finally-Blöcke für mehrere Ressourcen. FreeAndNil (anstelle von Free) löscht auch die Variable — nützlich zum Erkennen von Use-After-Free. Free ist sicher auf nil — keine Notwendigkeit, zuerst Assigned zu prüfen.

delphi
var
  List: TObjectList;
  Stream: TFileStream;
begin
  List := TObjectList.Create;
  try
    Stream := TFileStream.Create('data.bin', fmOpenRead);
    try
      // ... use Stream ...
    finally
      Stream.Free;
    end;
  finally
    List.Free;
  end;
end;

Schnittstellenbasierter Besitz

TInterfacedObject + Schnittstellenreferenz = automatische Bereinigung. Wenn die Schnittstelle den Gültigkeitsbereich verlässt, läuft der Destruktor. Dies ist RAII in Delphi — wickeln Sie Ressourcen in Schnittstellen-Objekte für garantierte Bereinigung ohne try-finally-Boilerplate.

delphi
type
  TTempFile = class(TInterfacedObject)
  private
    FName: string;
  public
    constructor Create(const AName: string);
    destructor Destroy; override;
  end;

destructor TTempFile.Destroy;
begin
  if FileExists(FName) then
    DeleteFile(FName);
  inherited;
end;

// usage:
var
  Temp: TTempFile;
begin
  Temp := TTempFile.Create('tmp.txt');
  // ... use file ...
end;  // Temp freed automatically (refcount)

Schwache Referenzen

Schwache Referenzen brechen Referenzzyklen. Ohne [Weak] würden zwei Objekte, die Schnittstellenreferenzen aufeinander halten, nie freigegeben (Zyklus). TComponent hat eingebautes FreeNotification-Mechanismus für schwache Referenzen. Das [Weak]-Attribut erfordert RTTI und funktioniert auf Schnittstellen- und Klassenfeldern.

delphi
type
  [Weak]
  FParent: TComponent;  // weak reference — no refcount increment

  // or for interfaces:
  [Weak]
  FLogger: ILogger;

// TComponent uses notification-based weak refs:
type
  TChild = class(TComponent)
  private
    FParent: TComponent;
  public
    property Parent: TComponent read FParent write FParent;
  end;

Records vs Objekte

Records sind Werttypen (Stack, bei Zuweisung kopiert) — keine Speicherverwaltung nötig. Klassen sind Referenztypen (Heap, müssen freigegeben werden). Verwenden Sie Records für kleine unveränderliche Daten (Punkte, Daten, Geld). Verwenden Sie Klassen für polymorphe oder große Objekte. Records können Methoden und Operatoren in modernem Delphi haben.

delphi
type
  TPoint = record  // value type — stack allocated
    X, Y: Double;
    function Distance: Double;
  end;

  TPointObj = class  // reference type — heap allocated
    X, Y: Double;
    function Distance: Double;
  end;

var
  P1, P2: TPoint;
  O1, O2: TPointObj;
begin
  P1 := P2;  // copies values
  O1 := O2;  // copies reference (both point to same object)
end;

Speicherleck-Erkennung

ReportMemoryLeaksOnShutdown zeigt einen Dialog beim Beenden mit geleakten Objekten. FastMM (der Standard-Speichermanager) erkennt Lecks, Double-Frees und Use-After-Free. Für Produktion protokollieren Sie Lecks in Datei. Führen Sie Leak-Prüfungen regelmäßig während der Entwicklung durch — leichter, Lecks zu beheben, wenn sie eingeführt werden.

delphi
uses
  System.ReportMemoryLeaksOnShutdown;

begin
  ReportMemoryLeaksOnShutdown := True;
  // ... your code ...
  // on app exit, leak report shown if any unfreed objects
end;

// FastMM4 (built into modern Delphi):
//   - Detects leaks with call stack
//   - Reports type and count of leaked objects
//   - Use FullDebugMode for detailed diagnostics

// Manual check:
var
  StartMem: Integer;
begin
  StartMem := AllocMemSize;
  // ... code under test ...
  if AllocMemSize > StartMem then
    Writeln('Memory leak detected');
end;
24

FireMonkey (FMX)

Plattformübergreifende Formular-Grundlagen

FMX-Formulare sind plattformübergreifend (Windows, macOS, iOS, Android, Linux). Gleicher Code, verschiedene native Renderer. Verwenden Sie FMX.*-Units anstelle von Vcl.*. Steuerelemente sind vektorbasiert (skalieren perfekt). Styles ersetzen Themes — visuelles Erscheinungsbild ist datengetrieben.

delphi
unit MainForm;

interface

uses
  System.SysUtils, System.Types, FMX.Forms, FMX.Controls,
  FMX.Controls.Presentation, FMX.Edit, FMX.Buttons;

type
  TFormMain = class(TForm)
    EditName: TEdit;
    ButtonSubmit: TSpeedButton;
    procedure ButtonSubmitClick(Sender: TObject);
  private
    FName: string;
  public
    property Name: string read FName;
  end;

var
  FormMain: TFormMain;

implementation

procedure TFormMain.ButtonSubmitClick(Sender: TObject);
begin
  FName := EditName.Text;
  Close;
end;

end.

Layouts und Ausrichtung

FMX verwendet Align (Client, Top, Bottom, Left, Right, None) und Margins/Padding für Layout. TFlowLayout arrangiert Kinder wie CSS Flexbox. TGridLayout erstellt ein Raster. Verwenden Sie TScaleBox für auflösungsunabhängige Skalierung. Layouts sind selbst Steuerelemente — verschachtelbar.

delphi
// Layout types: TLayout, TFlowLayout, TGridLayout, TScrollBox
var
  Layout: TFlowLayout;
  Btn: TButton;
begin
  Layout := TFlowLayout.Create(Self);
  Layout.Parent := Self;
  Layout.Align := TAlignLayout.Client;
  Layout.FlowDirection := TFlowDirection.LeftToRight;
  Layout.Justify := TJustifyMode.SpaceBetween;

  for var I := 1 to 5 do
  begin
    Btn := TButton.Create(Self);
    Btn.Parent := Layout;
    Btn.Text := 'Button ' + I.ToString;
    Btn.Margins.Rect := RectF(5, 5, 5, 5);
  end;
end;

Styles und Styling

Styles sind Sammlungen visueller Ressourcen (Pinsel, Schriftarten, Effekte), gespeichert in .fsf- oder .style-Dateien. StyleLookup wählt einen benannten Style für ein Steuerelement. TStyleManager schaltet globale Styles zur Laufzeit um. FMX-Styles sind Vektor — skalieren auf jedes DPI. Der Style Designer bearbeitet Styles visuell.

delphi
// Load a custom style
begin
  TStyleManager.LoadFromFile('Dark.fsf');
  TStyleManager.TrySetStyleFromResource('DarkStyle');
end;

// Apply style to a single control:
Button1.StyleLookup := 'cornerbutton';

// Read style in code:
var
  StyleObj: TFmxObject;
begin
  StyleObj := TStyleManager.ActiveStyle(Self).FindStyleResource('buttonstyle');
end;

// LiveBindings designer for visual data binding
// Tools > LiveBindings Designer

Effekte und Animationen

Effekte (Glow, Shadow, Blur, Reflection) sind nicht-visuelle Komponenten, die einem Steuerelement zugeordnet sind. Animationen (TFloatAnimation, TColorAnimation, TPathAnimation) animieren Eigenschaften über Zeit. Setzen Sie Parent auf das Ziel-Steuerelement. Trigger/Start zum Beginnen. Alle GPU-beschleunigt — flüssig auf allen Plattformen.

delphi
uses
  FMX.Effects, FMX.Ani;

var
  Glow: TGlowEffect;
  Ani: TFloatAnimation;
begin
  // Glow effect on a button
  Glow := TGlowEffect.Create(Button1);
  Glow.Parent := Button1;
  Glow.GlowColor := TAlphaColors.Blue;
  Glow.Enabled := True;

  // Animate opacity
  Ani := TFloatAnimation.Create(Button1);
  Ani.Parent := Button1;
  Ani.PropertyName := 'Opacity';
  Ani.StartValue := 0;
  Ani.EndValue := 1;
  Ani.Duration := 0.5;
  Ani.Start;
end;

Plattformdienste

Plattformdienste abstrahieren OS-spezifische Features. Abfragen mit SupportsPlatformService — gibt false auf nicht unterstützten Plattformen zurück. Prüfen Sie immer vor der Verwendung. Häufige Dienste: Zwischenablage, Dialoge, virtuelle Tastatur, Geräteinfo, Bildschirm. Dieses Muster hält Ihren Code plattformübergreifend ohne {$IFDEF}-Blöcke.

delphi
uses
  FMX.Platform;

var
  ScreenSvc: IFMXScreenService;
  Size: TPoint;
begin
  if TPlatformServices.Current.SupportsPlatformService(
    IFMXScreenService, IInterface(ScreenSvc)) then
  begin
    Size := ScreenSvc.GetScreenSize;
    Writeln(Size.X.ToString, 'x', Size.Y.ToString);
  end;
end;

// Other services:
//   IFMXClipboardService
//   IFMXDialogService (async message boxes)
//   IFMXVirtualKeyboardService
//   IFMXDeviceService
25

Datenbank (FireDAC)

Verbindungseinrichtung

TFDConnection ist das zentrale FireDAC-Objekt. Setzen Sie DriverName (SQLite, MSSQL, MySQL, PostgreSQL, Oracle usw.) und Params. Verbindungsdefinitionen können in einer .ini-Datei für Wiederverwendung gespeichert werden. Setzen Sie Connected := False immer vor dem Freigeben. Verwenden Sie einen TFDManager für Connection Pooling.

delphi
uses
  FireDAC.Comp.Client, FireDAC.Stan.Def;

var
  FDConn: TFDConnection;
begin
  FDConn := TFDConnection.Create(nil);
  try
    FDConn.DriverName := 'SQLite';
    FDConn.Params.Database := 'app.db';
    FDConn.Params.Add('Encrypt=AES-256');
    FDConn.Params.Password := 'secret';
    FDConn.Connected := True;

    // or use a connection definition file:
    // FDConn.ConnectionDefName := 'MySQLite';
  finally
    FDConn.Free;
  end;
end;

Abfrageausführung

Verwenden Sie Open für SELECT (gibt einen Cursor zurück), execSQL für INSERT/UPDATE/DELETE (gibt betroffene Zeilen zurück). Verwenden Sie IMMER Parameter — niemals Werte in SQL konkatenieren (Injektionsrisiko). ParamByName ist case-insensitiv. FieldByName greift auf Spalten nach Namen zu. Eof/Next iterieren Zeilen.

delphi
var
  Query: TFDQuery;
begin
  Query := TFDQuery.Create(nil);
  try
    Query.Connection := FDConn;

    // parameterized query (prevents SQL injection)
    Query.SQL.Text := 'SELECT * FROM users WHERE age > :min_age';
    Query.ParamByName('min_age').AsInteger := 18;
    Query.Open;

    while not Query.Eof do
    begin
      Writeln(Query.FieldByName('name').AsString);
      Query.Next;
    end;

    // execute non-query (INSERT/UPDATE/DELETE)
    Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:n, :a)';
    Query.ParamByName('n').AsString := 'Alice';
    Query.ParamByName('a').AsInteger := 30;
    Query.ExecSQL;
  finally
    Query.Free;
  end;
end;

Transaktionen

StartTransaction/Commit/Rollback umschließen atomare Operationen. Wenn eine Anweisung fehlschlägt, macht Rollback alle Änderungen rückgängig. Verschachtelte Transaktionen verwenden Savepoints (partielles Rollback). Umschließen Sie immer mit try-except-raise, um den Fehler nach Rollback weiterzugeben. Ohne Transaktion auto-committed jede Anweisung.

delphi
FDConn.StartTransaction;
try
  Query.SQL.Text := 'UPDATE accounts SET balance = balance - 100 WHERE id = 1';
  Query.ExecSQL;
  Query.SQL.Text := 'UPDATE accounts SET balance = balance + 100 WHERE id = 2';
  Query.ExecSQL;

  FDConn.Commit;
except
  FDConn.Rollback;
  raise;
end;

// nested transactions via savepoints:
FDConn.StartTransaction;
try
  // ... work ...
  FDConn.StartTransaction;  // savepoint
  try
    // ... more work ...
    FDConn.Commit;
  except
    FDConn.Rollback;  // rolls back to savepoint
  end;
finally
  FDConn.Commit;
end;

TFDTable und Live-Daten

TFDTable ist ein Live, editierbarer Cursor über eine Tabelle. Edit/Post modifiziert die aktuelle Zeile. Append/Post fügt ein. Delete entfernt die aktuelle Zeile. Änderungen gehen direkt an die Datenbank. Verwenden Sie IndexFieldNames für Sortierung. Für komplexe Abfragen verwenden Sie stattdessen TFDQuery.

delphi
var
  Table: TFDTable;
begin
  Table := TFDTable.Create(nil);
  try
    Table.Connection := FDConn;
    Table.TableName := 'users';
    Table.IndexFieldNames := 'name';  // ORDER BY
    Table.Open;  // SELECT * FROM users

    // edit current row
    Table.Edit;
    Table.FieldByName('age').AsInteger := 31;
    Table.Post;

    // insert new row
    Table.Append;
    Table.FieldByName('name').AsString := 'Bob';
    Table.FieldByName('age').AsInteger := 25;
    Table.Post;

    // delete current row
    Table.Delete;
  finally
    Table.Free;
  end;
end;

Batch-Updates und Cached-Modus

CachedUpdates-Modus puffert Änderungen im Speicher — wendet sie alle gleichzeitig mit ApplyUpdates an. Schneller als pro-Zeile-Updates für Massenoperationen. CancelUpdates verwirft den Puffer. Status zeigt den Änderungstyp pro Zeile. Nützlich für getrennte Szenarien und Reduzierung von Runden.

delphi
Query.CachedUpdates := True;
Query.Open;

// make many changes locally
while not Query.Eof do
begin
  Query.Edit;
  Query.FieldByName('status').AsString := 'processed';
  Query.Post;
  Query.Next;
end;

// apply all changes in one transaction
FDConn.StartTransaction;
try
  Query.ApplyUpdates;
  FDConn.Commit;
except
  FDConn.Rollback;
  Query.CancelUpdates;
  raise;
end;

// inspect change log:
Query.Status;  // TUpdateStatus (usModified, usInserted, usDeleted)
26

REST & HTTP

TRESTClient-Grundlagen

TRESTClient hält die Basis-URL. TRESTRequest erstellt die Anfrage (Methode, Ressource, Parameter). TRESTResponse hält das Ergebnis. URL-Segmente ({id}) werden durch AddUrlSegment substituiert. StatusCode/Content geben die HTTP-Antwort. In umgekehrter Reihenfolge der Erstellung freigeben.

delphi
uses
  REST.Client, REST.Types;

var
  Client: TRESTClient;
  Request: TRESTRequest;
  Response: TRESTResponse;
begin
  Client := TRESTClient.Create('https://api.example.com');
  Request := TRESTRequest.Create(Client);
  Response := TRESTResponse.Create(Client);
  try
    Request.Resource := '/users/{id}';
    Request.Method := TRESTRequestMethod.rmGET;
    Request.Params.AddUrlSegment('id', '42');
    Request.Params.AddItem('fields', 'name,email', TRESTRequestParameterKind.pkGETorPOST);

    Request.Execute;

    if Response.StatusCode = 200 then
      Writeln(Response.Content);
  finally
    Response.Free;
    Request.Free;
    Client.Free;
  end;
end;

JSON-Parsing

System.JSON stellt TJSONObject, TJSONArray, TJSONValue bereit. ParseJSONValue parst einen String (gibt TJSONValue zurück — casten Sie nach Bedarf). GetValue<T> liest typisierte Werte. AddPair/AddElement bauen JSON. Alle JSON-Objekte müssen freigegeben werden — sie sind nur referenzzählend, wenn sie einem Parent gehören.

delphi
uses
  System.JSON;

var
  JSON: TJSONObject;
  Arr: TJSONArray;
  Item: TJSONObject;
  I: Integer;
begin
  // parse
  JSON := TJSONObject.ParseJSONValue('{"name":"Alice","age":30}') as TJSONObject;
  try
    Writeln(JSON.GetValue<string>('name'));
    Writeln(JSON.GetValue<Integer>('age'));
  finally
    JSON.Free;
  end;

  // build
  JSON := TJSONObject.Create;
  try
    JSON.AddPair('name', 'Bob');
    JSON.AddPair('scores', TJSONArray.Create(90, 85, 92));
  finally
    JSON.Free;
  end;
end;

REST-Server mit DataSnap

DataSnap legt Delphi-Methoden automatisch als REST-Endpunkte offen. Methodennamen werden URL-Segmente. Parameter mappen auf URL-Segmente oder POST-Body. TJSONObject/TJSONArray sind die Standard-Rückgabetypen. Wenden Sie Attribute wie [httppost] an, um HTTP-Verben zu spezifizieren. Verwenden Sie TDSServerModule als Basisklasse.

delphi
// ServerContainerUnit1.pas
type
  TServerMethods1 = class(TDSServerModule)
    function GetUsers: TJSONArray;
    function GetUser(id: Integer): TJSONObject;
    [httppost] function CreateUser(Data: TJSONObject): TJSONObject;
  end;

function TServerMethods1.GetUsers: TJSONArray;
begin
  Result := TJSONArray.Create;
  // ... populate from DB ...
end;

// Access via URL:
//   GET  http://localhost:8080/datasnap/rest/TServerMethods1/GetUsers
//   GET  http://localhost:8080/datasnap/rest/TServerMethods1/GetUser/42
//   POST http://localhost:8080/datasnap/rest/TServerMethods1/CreateUser

Indy HTTP für niederschwellige Kontrolle

TIdHTTP (Indy) gibt volle Kontrolle über HTTP — Header, Cookies, Redirects, Timeouts. Ausführlicher als TRESTClient, aber flexibler. Für HTTPS weisen Sie einen SSL-IOHandler zu (TIdSSLIOHandlerSocketOpenSSL). Setzen Sie ReadTimeout/ConnectTimeout für Produktion. Indy ist synchron — wickeln Sie in TThread für Async ein.

delphi
uses
  IdHTTP, IdGlobal;

var
  Http: TIdHTTP;
  Response: string;
  PostData: TStringStream;
begin
  Http := TIdHTTP.Create(nil);
  try
    Http.Request.ContentType := 'application/json';
    Http.Request.CustomHeaders.AddValue('Authorization', 'Bearer token123');

    // GET
    Response := Http.Get('https://api.example.com/users');

    // POST
    PostData := TStringStream.Create('{"name":"Alice"}', TEncoding.UTF8);
    try
      Response := Http.Post('https://api.example.com/users', PostData);
    finally
      PostData.Free;
    end;

    Writeln(Http.ResponseCode);  // 200, 404, etc.
  finally
    Http.Free;
  end;
end;

Async HTTP mit Tasks

Wickeln Sie REST-Aufrufe in TTask.Run ein, um den UI-Thread nicht zu blockieren. Marshallen Sie UI-Aktualisierungen zurück mit TThread.Queue (async) oder TThread.Synchronize (sync). Seien Sie vorsichtig mit Objektlebenszyklen — die Anfrage muss den Task überleben. Ziehen Sie TRESTRequest.ExecuteAsync für eingebaute Async-Unterstützung in Betracht.

delphi
uses
  System.Threading, REST.Client;

var
  Request: TRESTRequest;
begin
  Request := TRESTRequest.Create(nil);
  try
    Request.Client := TRESTClient.Create('https://api.example.com/users');
    Request.Client.Owner := Request;

    TTask.Run(
      procedure
      begin
        Request.Execute;  // background thread

        TThread.Queue(nil,
          procedure
          begin
            // update UI on main thread
            Memo1.Lines.Text := Request.Response.Content;
          end);
      end);
  finally
    // don't free Request here — task may still be running
  end;
end;
27

Multithreading (Parallel)

TThread-Grundlagen

Leiten Sie von TThread ab und überschreiben Sie Execute. Create(False) startet sofort; Create(True) erfordert .Start. FreeOnTerminate := True gibt automatisch frei — rufen Sie niemals Free auf solchen Threads auf. Prüfen Sie Terminated periodisch für graceful Shutdown. Greifen Sie niemals auf UI aus Execute zu — verwenden Sie Synchronize/Queue.

delphi
type
  TWorker = class(TThread)
  protected
    procedure Execute; override;
  public
    constructor Create;
  end;

procedure TWorker.Execute;
var
  I: Integer;
begin
  for I := 1 to 100 do
  begin
    if Terminated then Exit;
    // ... work ...
    Sleep(50);
  end;
end;

constructor TWorker.Create;
begin
  inherited Create(False);  // False = start immediately
  FreeOnTerminate := True;  // auto-free on completion
end;

TTask und Futures

ITask/IFuture<T> aus System.Threading sind höherwertig als TThread. Futures geben einen typisierten Wert zurück — .Value blockiert, bis das Ergebnis bereit ist. Tasks sind referenzzählend (kein manuelles Free). Verwenden Sie TTask.WaitForAll / WaitForAny, um mehrere Tasks zu koordinieren. Leichter zu verwenden als rohes TThread.

delphi
uses
  System.Threading;

var
  Task: ITask;
  Future: IFuture<string>;
begin
  // fire-and-forget task
  Task := TTask.Create(
    procedure
    begin
      // ... background work ...
    end);
  Task.Start;

  // future — returns a value
  Future := TTask.Future<string>(
    function: string
    begin
      Sleep(1000);
      Result := 'computed value';
    end);

  // ... do other work ...

  Writeln(Future.Value);  // blocks until ready
end;

Parallele For-Schleife

TParallel.For führt Schleifeniterationen parallel über CPU-Kerne aus. MÜSSEN gemeinsamen Zustand synchronisieren (verwenden Sie TCriticalSection oder TInterlocked). Iterationsreihenfolge ist nicht-deterministisch. Verwenden Sie TLoopState für Break/Continue. Schneller für CPU-gebundene Arbeit; langsamer für triviale Iterationen aufgrund von Overhead.

delphi
uses
  System.Threading, System.SyncObjs;

var
  Total: Integer;
  Lock: TCriticalSection;
  I: Integer;
begin
  Lock := TCriticalSection.Create;
  try
    TParallel.For(1, 1000,
      procedure(I: Integer)
      begin
        Lock.Enter;
        try
          Total := Total + ComputeExpensive(I);
        finally
          Lock.Leave;
        end;
      end);
  finally
    Lock.Free;
  end;
end;

Synchronisationsprimitive

TCriticalSection: gegenseitiger Ausschluss (nur ein Thread gleichzeitig). TEvent: Signal zwischen Threads (SetEvent/WaitFor). TEvent mit manuellem Reset bleibt signalisiert bis Reset. TInterlocked.Increment ist atomar und schneller als eine Critical Section für einfache Zähler. TMonitor (in TObject eingebaut) ist eine weitere Option.

delphi
uses
  System.SyncObjs;

var
  Lock: TCriticalSection;
  Event: TEvent;
  Count: Integer;
begin
  Lock := TCriticalSection.Create;
  Event := TEvent.Create(nil, True, False, '');
  try
    TThread.CreateAnonymousThread(
      procedure
      begin
        Lock.Enter;
        try
          Inc(Count);
        finally
          Lock.Leave;
        end;
        Event.SetEvent;  // signal completion
      end).Start;

    Event.WaitFor(INFINITE);  // wait for signal
  finally
    Lock.Free;
    Event.Free;
  end;
end;

TThread.Queue und Synchronize

UI-Steuerelemente können nur vom Haupt-Thread berührt werden. Synchronize blockiert den Worker, bis der Haupt-Thread die anonyme Methode ausführt — verwenden Sie es sparsam (verursacht Serialisierung). Queue postet und fährt fort — bevorzugt für Fire-and-Forget-UI-Aktualisierungen. Übergeben Sie nil als Thread-Argument, um den aktuellen Thread zu verwenden.

delphi
// From a worker thread, update UI safely:

// Synchronous — blocks worker until main thread runs the code
TThread.Synchronize(nil,
  procedure
  begin
    Label1.Caption := 'Done';
  end);

// Asynchronous — posts to main thread queue, doesn't block
TThread.Queue(nil,
  procedure
  begin
    Label1.Caption := 'Progress: 50%';
  end);

// TThread.Queue is preferred for non-critical updates
// Synchronize for cases where you need the result before continuing
28

Packages & Komponenten

Package-Projekt-Grundlagen

Packages (.bpl) sind DLLs mit Delphi-Metadaten — teilen Code zwischen Apps. 'requires' listet Abhängigkeiten auf. 'contains' listet Units in diesem Package auf. Design-Time-Packages installieren Komponenten in die IDE; Runtime-Packages werden mit der App ausgeliefert. Trennen Sie Design/Runtime, um IDE-Bloat gering zu halten.

delphi
// MyPackage.dpk
package MyPackage;

{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$DESIGNONLY MyDesignUnits}  // design-time only
{$RUNONLY MyRuntimeUnits}    // runtime only

requires
  rtl,
  vcl,
  System.Generics.Collections;

contains
  MyUnit1 in 'MyUnit1.pas',
  MyUnit2 in 'MyUnit2.pas',
  MyComponent in 'MyComponent.pas';

end.

// Build configurations:
//   - Build (debug)
//   - Release
//   - Design-time (installs into IDE)
//   - Runtime (deployed with app)

Benutzerdefinierte Komponenten-Skeleton

Leiten Sie von der nächsten vorhandenen Klasse ab (TCustomLabel gibt ein Label ohne veröffentlichte Eigenschaften). Veröffentlichen Sie nur die Eigenschaften, die Sie offengelegt möchten. Die Register-Prozedur fügt die Komponente der IDE-Palette hinzu. 'default' setzt den Anfangswert (muss dem Konstruktor entsprechen). Platzieren Sie Register in einem Design-Time-Package.

delphi
unit MyLabel;

interface

uses
  Vcl.Controls, Vcl.Graphics, Vcl.StdCtrls;

type
  TMyLabel = class(TCustomLabel)
  private
    FHighlightColor: TColor;
    procedure SetHighlightColor(Value: TColor);
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property HighlightColor: TColor
      read FHighlightColor write SetHighlightColor default clYellow;
    property Caption;
    property Font;
    property OnClick;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('MyPalette', [TMyLabel]);
end;

Komponenteneigenschaften und -editoren

TComponent ist die Basis für nicht-visuelle Komponenten. Besitzen Sie Sub-Objekte (FItems) — erstellen Sie im Konstruktor, geben Sie im Destruktor frei. TStrings-Eigenschaften erhalten einen eingebauten String-Editor. RegisterPropertyEditor passt den Objektinspektor für spezifische Eigenschaften an. Verwenden Sie TPersistent für verschachtelte Objekte, die Streaming benötigen.

delphi
type
  TMyComponent = class(TComponent)
  private
    FItems: TStringList;
    function GetItems: TStrings;
    procedure SetItems(Value: TStrings);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Items: TStrings read GetItems write SetItems;
  end;

constructor TMyComponent.Create(AOwner: TComponent);
begin
  inherited;
  FItems := TStringList.Create;
end;

destructor TMyComponent.Destroy;
begin
  FItems.Free;
  inherited;
end;

// Property editor for rich editing in Object Inspector:
//   RegisterPropertyEditor(TypeInfo(TStrings), TMyComponent, 'Items',
//     TStringListProperty);

Ereignisse und Methodenzeiger

Ereignistypen sind prozedurale Typen mit 'of object' — sie halten sowohl eine Objektreferenz als auch einen Methodenzeiger. Prüfen Sie immer Assigned(), bevor Sie aufrufen — nil-Ereignisse werfen AVs. Do*-Methoden (DoChange, DoClick) sind die geschützten Dispatcher, die Ereignisse feuern. Unterklassen können Do* überschreiben, um Ereignisse abzufangen.

delphi
type
  TMyEvent = procedure(Sender: TObject; Value: Integer) of object;

  TMyComponent = class(TComponent)
  private
    FOnChange: TMyEvent;
  protected
    procedure DoChange(Value: Integer);
  published
    property OnChange: TMyEvent read FOnChange write FOnChange;
  end;

procedure TMyComponent.DoChange(Value: Integer);
begin
  if Assigned(FOnChange) then
    FOnChange(Self, Value);
end;

// 'of object' makes it a method pointer — must be assigned to a method
// (e.g., Form1.Button1Click). Always check Assigned before calling.

Streaming und Persistenz

TPersistent ermöglicht Streaming und Assign. Veröffentlichte Eigenschaften werden automatisch in DFM-Dateien gespeichert. Überschreiben Sie Assign, um das Kopieren zwischen Objekten zu unterstützen. DefineProperties fügt nicht-veröffentlichte Daten zum Stream hinzu. WriteComponent/ReadComponent serialisieren zu jedem TStream. So persistieren Formulare ihren Zustand.

delphi
type
  TMySettings = class(TPersistent)
  private
    FTimeout: Integer;
    FTitle: string;
  published
    property Timeout: Integer read FTimeout write FTimeout default 30;
    property Title: string read FTitle write FTitle;
  end;

// TPersistent enables streaming (DFM, RTTI):
//   - Inherits from TPersistent (gives Assign)
//   - Published properties are streamed
//   - Override AssignTo/Assign for custom copy

// Save to DFM automatically:
//   - Component owned by a form is streamed
//   - Sub-properties (TPersistent) are nested in DFM
//   - Use DefineProperties for non-standard data

// Manual streaming:
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create('settings.bin', fmCreate);
  try
    Stream.WriteComponent(MyComponent);
  finally
    Stream.Free;
  end;
end;

Was this helpful?