Skip to content

Delphi 速查表

用于快速应用开发的 Object Pascal 方言。

01

程序结构与基础

程序结构与单元

Delphi 程序以 'program' 开始,以 'end.'(带句点)结束。{$APPTYPE CONSOLE} 是编译器指令,将其标记为控制台应用。'uses' 导入单元(模块)—— System.SysUtils 有 Format、IntToStr 等。单元有 interface 部分(公共声明)和 implementation 部分(代码)。WriteLn 输出带换行的文本;Write 输出不带换行。ReadLn 读取输入(或暂停)。主 begin..end 块是程序的入口点。

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.

变量、类型与常量

Delphi 是强类型的。常见类型:Integer(32 位)、Int64(64 位)、Double(64 位浮点)、Extended(x86 上 80 位浮点)、Single(32 位浮点)、string(Unicode,引用计数)、Char(WideChar,2 字节)、Boolean、Byte(0-255)。TDateTime 实际上是 Double(自 1899-12-30 以来的天数)。常量使用 'const' —— 类型化常量有类型,非类型化常量灵活。子范围类型(0..150)限制值。枚举(TDay)定义命名常量。Format() 类似 sprintf:%s(字符串)、%d(整数)、%f(浮点)。

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;

运算符与表达式

Delphi 使用 := 进行赋值,使用 = 进行相等比较(与类 C 语言相反)。div 是整数除法;/ 是实数除法(始终返回 Extended/Double)。mod 是取余。and/or/not/xor 适用于布尔值(逻辑)和整数(按位)—— 由上下文决定。shl/shr 是位移。Inc/Dec 是高效的就地递增/递减(避免写 A := A + 1)。字符串连接使用 +。Power() 在 System.Math 中。:= vs = 的区别是初学者错误的头号来源。

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;

输入、输出与格式化

Format() 是 Delphi 的 sprintf —— 使用 %s(字符串)、%d(整数)、%f(浮点)、%x(十六进制)、%m(货币),带宽度/精度修饰符。WriteLn(value:width:decimals) 直接格式化浮点数。ReadLn 将输入读入变量。StrToInt/StrToFloat 将字符串转换为数字(失败时抛出 EConvertError);TryStrToInt 返回 Boolean,更安全。IntToStr/FloatToStr 将数字转换为字符串。FormatDateTime 格式化日期(yyyy、mm、dd、hh、nn、ss)。FloatToStrF 提供精确控制(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;

单元、作用域与可见性

单元是 Delphi 的模块。interface 部分声明公共内容(对用户可见);implementation 包含代码,可以有私有类型/变量。initialization/finalization 部分在单元加载/卸载时运行(类似单元的构造函数/析构函数)。interface 中声明的变量是全局的;implementation 中的是单元私有的。interface 中的类型是公共的;implementation 中的是私有的。这种两段式设计在单元级别强制封装。'uses' 子句导入其他单元 —— 用 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

控制流

If...Then...Else

If...Then...Else 是 Delphi 的条件语句。关键:'else' 前没有分号 —— 分号结束语句,而 else 是 if 的一部分。对于多语句分支,用 begin..end 包装(else 前仍无分号)。and/or/not 是逻辑运算符(对整数也是按位)。使用括号分组条件:(A > 0) and (B > 0)。else 前缺少分号是初学者最常见的 Delphi 语法错误。

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)语句

Case 是 Delphi 的 switch —— 适用于序数类型(Integer、Char、枚举、子范围)。每个分支可以是单个值、逗号分隔列表('D', 'F')或范围(1..5)。else 子句是默认分支。Case 不会 fall-through(不同于 C)。对于多语句分支,使用 begin..end。对于离散值,Case 比链式 if-else 更清晰。你不能直接对字符串使用 case(使用 if-else 或查找表)。

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 循环(To、Downto、In)

For...to 升序迭代;For...downto 降序迭代。循环变量不能在循环内修改。For...in(现代 Delphi)迭代数组、字符串(逐字符)、集合和任何可枚举对象。Break 退出循环;Continue 跳到下一次迭代。没有内置步长 —— 使用条件或 while 循环。循环变量在循环后未定义(不要依赖其值)。For...in 优先用于集合(更清晰,无索引错误)。

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 在循环体前测试(可能永不执行);repeat...until 在之后测试(始终至少运行一次)。关键:while 在条件为 TRUE 时继续;repeat 在条件为 TRUE 时停止(相反的逻辑!)。repeat...until 不需要 begin..end(它本身就是一个块)。使用 while 表示"零次或多次",使用 repeat 表示"一次或多次"。Break 退出;Continue 跳到测试。while True 配合 Break 是带复杂退出条件循环的常见惯用法。

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 访问记录/对象的成员而无需重复变量名 —— 适用于初始化和减少冗余。避免嵌套 With(关于成员属于哪个对象的歧义)。Goto 跳转到标签 —— 在现代 Delphi 中很少使用(优先使用 Break/Continue/Exit);用 'label' 声明标签。Exit 立即离开过程;Exit(value) 从函数返回值(现代语法)。With 如果过度使用会使代码可读性降低 —— 谨慎用于简单情况。

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

字符串与文本处理

字符串类型与操作

Delphi 的默认字符串是 UnicodeString(UTF-16,引用计数,写时复制)。字符串从 1 开始索引(S[1] 是第一个字符)—— C 程序员常见的 bug 来源。Length() 返回字符数。Pos() 查找子字符串(未找到返回 0,而非 -1)。Copy() 提取子字符串(Start, Count)。StringReplace 替换(rfReplaceAll 替换所有出现)。Trim/TrimLeft/TrimRight 移除空白。Split/Join 是现代方法(TArray<string>)。使用 SameText 进行不区分大小写的比较。

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;

字符串格式化与转换

Format() 是 Delphi 的 sprintf:%d(整数)、%f(浮点)、%s(字符串)、%x(十六进制)、%m(货币),带宽度/精度修饰符。FloatToStrF 提供精确控制(ffFixed、ffCurrency、ffNumber、ffExponent)。FormatDateTime 格式化日期:yyyy(4 位年)、mm(月)、dd(日)、hh(小时)、nn(分钟)、ss(秒)、dddd(完整星期名)、mmmm(完整月份名)。StrToInt/StrToFloat 在无效输入时抛出 EConvertError;TryStrToInt 返回 Boolean(更安全)。对用户输入始终使用 Try...。

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(可变)对构建大字符串的循环很高效 —— Append 就地修改而非创建新字符串。TStringList 是 Delphi 的瑞士军刀:字符串列表,可以排序、搜索、持有键值对(Values[])、加载/保存文件(每项一行)、拆分分隔文本(CommaText、DelimitedText)。TStringList 从 0 开始索引(SL[0]),不同于字符串(S[1])。始终包装在 try..finally 中以 Free。它是 Delphi 中处理文本文件和简单配置最常见的方式。

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 是 2 字节 Unicode 字符。Ord() 获取码点;Char() 转换回来。IsDigit/IsLetter/IsWhiteSpace/IsUpper/IsLower 分类字符。ToUpper/ToLower 转换大小写。TEncoding.UTF8.GetBytes 将字符串转换为字节数组(对文件 I/O 和网络必不可少)—— UTF-8 每字符使用 1-4 字节。TEncoding.Unicode 是 UTF-16 LE(始终 2 字节/字符)。Base64(TNetEncoding.Base64)将二进制数据编码为文本以传输。String 和 Char 内部是 UTF-16;为文件存储和网络协议转换为 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;

正则表达式

System.RegularExpressions 提供 TRegex 进行模式匹配。IsMatch 测试;Match 查找第一个;Matches 查找所有。组用括号捕获部分 —— 通过 Groups[1]、Groups[2] 访问(1 开始索引)。Replace 替换匹配($1、$2 引用组)。Split 按模式拆分。常见正则:\d(数字)、\w(单词字符)、\s(空白)、+(一个或多个)、*(零个或多个)、{n}(恰好 n 个)、^/$(开始/结束)。roCompiled 编译以加快重复使用。始终用正则验证用户输入(电子邮件、电话)。

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

数组、记录与集合

静态与动态数组

静态数组在编译时固定大小,带自定义索引范围(array[0..4] 或 array[1..7])。动态数组(array of T)可用 SetLength 调整大小 —— 它们从 0 开始索引且引用计数。High() 返回最后一个索引;Length() 返回计数。对现有动态数组 SetLength 调整大小(增长时保留现有值)。设置为 nil 以释放。动态数组字面量使用 [1, 2, 3]。多维动态数组是"数组的数组"(锯齿)—— 每行可以有不同的长度。

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;

记录(结构体)

记录是值类型(赋值时复制,栈分配)—— 类似 C 的结构体。现代 Delphi 记录可以有方法、属性和可见性(private/public)。记录不需要释放(无堆分配)。变体记录(case...of)创建字段共享内存的联合 —— 适用于类型标签。对小型、轻量级数据(点、坐标、配置)使用记录。对需要继承或多态的较大对象使用类。记录更快(无堆分配)但不能被继承。

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;

集合与枚举

集合是 Delphi 的独特特性 —— 来自枚举或子范围的值集合(最多 256 个元素)。运算符:+(并集)、-(差集)、*(交集)、=(相等)、<=(子集)、in(成员资格)。Include/Exclude 是高效的单元素添加/删除。集合存储为位图(非常快)。常见用途:TFontStyles(fsBold、fsItalic)、用于验证的字符集合(['0'..'9'])、星期几。枚举是序数类型 —— 用 Low() 到 High() 迭代,用 GetEnumName 转换为字符串。集合使标志组合优雅且类型安全。

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 与泛型

System.Generics.Collections 提供类型安全的集合:TList<T>(动态数组)、TDictionary<K,V>(哈希映射)、TQueue<T>(FIFO)、TStack<T>(LIFO)、THashSet<T>(唯一元素)。所有都是泛型的(编译时类型检查,无转换)。TList 有 Add/Remove/Delete/Sort/Contains/IndexOf。TDictionary 有 Add/Remove/TryGetValue/Keys/Values。TObjectList<T> 拥有其对象(自动释放它们)—— 当列表应管理对象生命周期时使用它。始终包装在 try..finally 中以 Free(这些是对象,不是记录)。

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;

数组算法与排序

TArray 是数组操作的实用工具类:Sort(带可选自定义 IComparer)、BinarySearch(在排序数组上快速搜索)、Reverse、Copy。TComparer<T>.Construct 内联创建比较函数(匿名方法)。按字段排序需要自定义比较器。BinarySearch 返回 Boolean 和找到的索引 —— 数组必须先排序。对于复杂搜索,带 Break 的线性循环简单清晰。TArray.Sort 是快速排序(平均 O(n log n))。

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

过程、函数与参数

过程与函数

过程(无返回值)和函数(返回值)是 Delphi 的子程序。Result 变量是返回值 —— 分配给它(函数在结束时返回)。Exit() 立即返回带值(现代语法)。前向声明让你在函数体定义之前调用它(适用于相互递归)。函数可以返回任何类型,包括记录、数组和对象。不带值的 Exit 只是离开过程。Result 变量是隐式声明的,匹配返回类型。

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;

参数:Const、Var、Out、Default

const:只读参数(也避免复制字符串/数组 —— 高效)。var:按引用传递(修改调用者的变量 —— 类似 C# 的 ref)。out:仅输出(调用者不初始化;函数设置它)。默认参数必须最后。开放数组参数(array of T)接受任何数组或字面量 [1,2,3] —— 使用 const 提高效率。const 优先用于字符串和数组(无复制);仅在需要修改调用者的值时使用 var。开放数组从 0 开始索引,无论源数组的边界如何。

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;

重载与默认参数

重载允许多个例程共享一个名称但参数列表不同 —— 编译器选择最佳匹配。需要 'overload' 指令。重载比发明不同名称(AddInt、AddDouble)更清晰。默认参数是替代方案 —— 调用者可以省略它们。当逻辑因类型而异时优先使用重载;对可选值使用默认值。歧义(两个匹配相同的重载)是编译错误。重载必须在参数数量或类型上不同(仅返回类型不够)。

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;

匿名方法与闭包

匿名方法(闭包)是分配给 'reference to' 类型的内联函数/过程。它们从封闭作用域捕获变量(闭包)。'reference to function'/'reference to procedure' 是委托类型。匿名方法启用函数式编程:高阶函数(Apply 接受一个函数)、闭包(MakeMultiplier 返回一个记住 Factor 的函数)和自定义比较器(TComparer<T>.Construct)。它们对泛型排序、事件处理器和回调至关重要。捕获的变量是堆分配的(它们比封闭函数寿命长)。

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;

递归与辅助例程

递归是函数调用自身 —— 需要基本情况来终止。阶乘和斐波那契是经典示例。尾递归(递归调用是最后操作)可由编译器优化。嵌套过程/函数在另一个例程内声明,可以访问其变量(词法作用域)—— 适用于不需要在外部可见的辅助函数。注意深度递归的栈溢出(对大输入使用迭代)。记忆化(缓存结果)可以加速斐波那契等递归算法。

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

类与 OOP

类定义、构造函数与析构函数

类是引用类型(堆分配,通过指针访问)。Create 是构造函数;Destroy 是析构函数(始终重写;由 Free 调用)。'inherited' 调用基类的方法。字段按约定使用 F 前缀。属性(property X: Type read GetX write SetX)提供受控访问 —— 调用者使用 P.Age 但 setter 进行验证。可见性:private(旧版 Delphi 中仅单元;strict private 是真正的私有)、protected(子类)、public(所有人)、published(RTTI,用于窗体/检查器)。始终将对象创建包装在 try..finally 中以确保调用 Free。

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;

属性与索引属性

属性用 getter/setter 封装字段访问。只读属性只有 'read' 说明符。'default' 指令使索引属性成为默认 —— 这样 L[i] 可用而非 L.Items[i]。属性可以有直接字段访问(read FCount)或方法访问(read GetItem write SetItem)进行验证/计算。索引属性启用类似数组的语法。Published 属性(published 部分)对 RTTI 和窗体设计器可见。属性是 Delphi 安全公开数据的方式 —— 始终优先使用它们而非公共字段。

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;

继承与多态

继承:TDog = class(TAnimal) 表示 TDog 继承自 TAnimal。'virtual' 标记方法用于多态;'override' 在子类中替换它。运行时,实际对象的方法运行(虚拟分派)—— 在持有 TDog 的 TAnimal 引用上调用 Speak 会调用 TDog.Speak。静态方法(Move)由变量的类型决定,而非对象的。'inherited' 调用基方法。构造函数可以是虚拟的(工厂模式)。使用 virtual/override 实现多态;行为固定时使用静态方法。始终释放你创建的对象。

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;

抽象方法与类方法

抽象类(class abstract)不能被实例化 —— 它们为子类定义契约。抽象方法(virtual; abstract)没有实现 —— 子类必须重写它们。这强制每个形状提供 Area/Perimeter。类方法(class function/procedure)不需要实例 —— 通过 TShape.ShapeCount 调用。类变量(class var)在所有实例间共享。模板方法模式:TShape.Describe 调用抽象的 Area/Perimeter,由子类填充。抽象方法定义"做什么";子类定义"如何做"。

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;

接口与多重继承

接口是纯契约(无字段,无实现)—— Delphi 实现类型多重继承的方式。一个类可以实现多个接口(TButton 实现 IComparable、IDrawable、IDisposable)。接口可以有 GUID 用于 QueryInterface/as 转换。TInterfacedObject 提供引用计数 —— 当最后一个接口引用超出作用域时,对象自动释放(不要调用 Free!)。使用接口解耦:代码依赖 IDrawable,而非 TButton。'as' 运算符转换为接口(不支持时抛出)。接口是 Delphi COM 支持和现代插件架构的支柱。

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

异常与错误处理

Try...Except...Finally

try...except 捕获异常(类似 C# 的 try/catch)。每个 'on E: ExceptionType do' 处理特定异常。try...finally 确保无论是否有异常都运行清理(无异常处理 —— 用于 Free 调用)。模式是 try...try...except...finally(内部 except 处理,外部 finally 清理)。'raise'(裸)重新引发当前异常。Exception 是基类;EFileNotFoundException、EInOutError 是子类。始终先放最特定的异常,Exception(基类)最后。切勿留空 except(静默吞掉错误)。

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;

引发与自定义异常

Raise 创建异常:raise ExceptionType.Create('message')。CreateFmt 类似 Format + Create。自定义异常继承自 Exception(或特定子类),可以携带额外数据(TransactionId)。包装时,通过 SetInner 或构造函数参数保留原始异常。自定义异常让调用者捕获特定错误类型:将 ETransactionError 与 ERangeError 分开捕获。始终包含有意义的消息。常见内置异常: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;

断言与调试

Assert 检查条件,如果为 false 则引发 EAssertionFailed —— 用于不变量(必须始终为真的条件)。断言用 {$C-} 禁用(或在发布构建中移除)—— 不要用于输入验证(使用异常)。OutputDebugString 记录到 IDE 的事件日志(无文件 I/O)。TStopwatch 精确测量经过的时间。Exception.StackTrace 需要调试信息(.map 文件或 JCLDebug/FastMM)。{$IFDEF DEBUG} 启用仅调试代码。对内部逻辑错误使用断言,对用户/外部错误使用异常。

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;

异常处理模式

常见异常模式:(1) 重试循环 —— 在 while 循环内的 try/except 中包装易失败操作,MaxRetries 后重新引发。(2) 回退值 —— 捕获特定异常(EConvertError)并返回默认值;仅吞掉你真正预期的异常。(3) 资源保护 —— 始终在 try/finally 中包装 Create/Free,以便即使在异常时对象也被释放(这是最重要的 Delphi 惯用法)。(4) 多个资源 —— 嵌套 try/finally 块;在每个自己的保护块中获取每个资源。(5) 验证 —— 尽早引发特定异常类型(EArgumentException、ERangeError),带描述性消息。切勿捕获 Exception 并静默继续 —— 至少记录它。优先使用 try/finally 进行清理,try/except 进行真正的恢复。

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;

日志与错误报告

生产日志器需要:(1) 线程安全 —— TCriticalSection 序列化写入(多线程可能并发记录)。(2) 严重性级别 —— TLogLevel 枚举让你过滤(例如,在生产中抑制 llDebug)。(3) 格式化输出 —— 每行 DateTime + 级别 + 消息,稍后可解析。(4) 每次写入后刷新 —— 以便日志在崩溃中存活(未刷新的缓冲写入在 AV 时丢失)。(5) 异常日志记录 —— LogException 捕获 ClassName + Message + 上下文。记录并重新引发模式记录错误但仍让上层处理它。对于高性能日志记录,考虑无锁队列或外部库(如 Log4Delphi)。始终在 finally 中 Free 日志器以关闭文件句柄。

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

文件 I/O 与流

文本文件(旧版与现代)

两种方法:旧版(AssignFile/Reset/Rewrite/ReadLn/WriteLn/CloseFile)是经典 Pascal —— 适用于简单文本 I/O 但容易出错(默认无异常)。现代(System.IOUtils 中的 TFile)更清晰:WriteAllText、ReadAllText、ReadAllLines、AppendAllText、Exists。TFile 方法在错误时引发异常(使用 try...except)。对于大文件,使用 StreamReader/StreamWriter(逐行,低内存)。始终关闭文件(旧版用 CloseFile,或使用 try..finally)。TFile 优先用于新代码 —— 它更安全且更一致。

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;

用于文件与 CSV 的 TStringList

TStringList 是处理文本文件和简单 CSV 的最简单方式。LoadFromFile/SaveToFile 读/写整个文件(每项一行)。CommaText 拆分/连接逗号分隔值;DelimitedText 使用自定义 Delimiter。Values[] 处理键值对(类似简单 INI 文件)。Sorted=True 自动排序;Find 执行二分搜索(比排序列表上的 IndexOf 快)。Duplicates 控制添加重复项的行为(dupIgnore、dupAccept、dupError)。对于复杂 CSV(带逗号的引号字段),使用专用 CSV 解析器。TStringList 从 0 开始索引。

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;

流与二进制 I/O

TFileStream 是低级字节 I/O(Read/Write 缓冲区,Position 用于定位)。TBinaryWriter/Reader 写/读类型化值(Int32、Double、String、Boolean)—— 读取顺序必须匹配写入顺序。TStreamReader/Writer 处理带编码的文本(UTF-8、ASCII、Unicode)—— 用于带非 ASCII 字符的文本文件。所有流都必须释放(try..finally)。fmCreate 创建/覆盖;fmOpenRead 只读打开;fmOpenWrite 为写入打开。对于大文件,使用 StreamReader 逐行读取(低内存)而非 LoadFromFile(加载整个文件)。

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;

目录与路径操作

System.IOUtils 为现代文件操作提供 TPath、TFile、TDirectory。TPath.Combine 安全连接路径(跨平台)。TPath.GetTempFileName 创建唯一的临时文件。TDirectory.GetFiles 支持搜索模式和递归搜索(soAllDirectories)。TFile.Copy/Move/Delete 是简单的文件操作。TFileInfo 提供文件元数据(大小、时间戳)。始终使用 TPath 方法而非字符串连接处理路径(正确处理分隔符)。这些类在 Windows、macOS 和 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 文件与 JSON

TIniFile 读/写 INI 配置文件([方括号] 中的节,键=值)。ReadString/ReadInteger/ReadBool 有默认值(键缺失时返回)。INI 文件是简单、人类可读的配置 —— 适用于用户首选项。对于结构化数据,使用 JSON(System.JSON)。TJSONObject 构建/解析 JSON 对象;TJSONArray 用于数组。AddPair 添加键值;GetValue<T> 检索类型化值。ParseJSONValue 解析 JSON 字符串。JSON 非常适合 API、复杂配置和数据交换。对于 REST 客户端,使用 TRESTClient 或 Indy 组件。

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 组件深入

窗体与组件生命周期

VCL 窗体遵循严格的生命周期:OnCreate(分配资源,初始化)→ OnShow(窗体变为可见)→ OnActivate → OnResize → OnPaint → ... → OnCloseQuery(可取消关闭)→ OnClose → OnDestroy(释放资源)。始终将 OnCreate 与 OnDestroy 配对进行资源管理。OnCloseQuery 让你阻止关闭(设置 CanClose := False)。Sender 是触发事件的组件。组件拥有其子组件 —— 释放窗体会自动释放其所有组件。

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;

常用 VCL 控件

VCL 提供丰富的控件集:TEdit(单行文本)、TMemo(多行文本)、TLabel(不可编辑文本)、TButton、TCheckBox、TRadioButton、TComboBox(下拉)、TListBox(可选列表)。TStrings 是基础集合(Lines、Items 是 TStrings)。ItemIndex 选择项(0 开始,-1 = 无)。ComboBox 样式:csDropDown(可编辑)、csDropDownList(只读)。RadioGroup 用 ItemIndex 分组单选按钮。Sorted 自动排序项。PasswordChar 在 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 在类似电子表格的网格中显示表格数据。Cells[Col, Row] 访问单个单元格(0 开始索引)。FixedRows/FixedCols 创建非滚动标题。ColWidths/RowHeights 自定义大小。Options 如 goEditing(可编辑单元格)、goColSizing(调整列大小)、goRowSelect 启用行为。OnDrawCell 允许使用 Canvas 自定义渲染。TDBGrid 通过 TDataSource 直接连接到 DataSet(TTable、TQuery)—— 它自动显示和编辑数据库记录。对数据库数据使用 TDBGrid,对内存数据使用 TStringGrid。

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 使用 TTreeNode 对象显示层次(树)数据。AddChild 创建嵌套节点。Expand(True) 递归展开。GetNext 深度优先遍历;GetNextSibling 逐级遍历。BeginUpdate/EndUpdate 批量更改以提高性能。TListView 以各种视图样式显示项:vsIcon、vsSmallIcon、vsList、vsReport(列)。Caption 是第一列;SubItems 持有后续列。两者都通过 OnGetNodeData/OnData 事件支持 owner-data(虚拟)模式用于大型数据集。

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');

对话框与常用组件

Delphi 提供标准对话框组件:TOpenDialog/TSaveDialog(文件选择)、TOpenPictureDialog(图像预览)、TColorDialog、TFontDialog、TPrintDialog。Execute 在用户点击 OK 时返回 True。Filter 设置文件类型模式('Description|*.ext')。MessageDlg 显示带类型(mtInformation、mtWarning、mtError、mtConfirmation)和按钮集([mbYes, mbNo, mbOK, mbCancel])的模态消息框。InputBox/InputQuery 获取用户文本输入。TPageControl 用 TTabSheet 页面管理选项卡式界面。所有对话框都是放置在窗体上的非可视组件。

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

事件驱动编程

事件与事件处理器

Delphi 中的事件是方法指针(procedure of object)。TNotifyEvent 是标准事件类型:procedure(Sender: TObject) of object。事件是属性 —— 在设计时(对象检查器)或运行时分配处理器。调用事件处理器前始终检查 Assigned()(如果未分配,它可能是 nil)。Sender 是触发事件的对象。自定义事件使用 'of object' 绑定到实例方法。var 参数(如 OnKeyPress 中的 var Key: Char)让处理器修改值 —— 设置 Key := #0 以抑制输入。

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;

委托与方法指针

方法指针('of object')携带方法地址和对象实例 —— 它们是对 Self 的闭包。常规过程指针(不带 'of object')指向独立函数。方法指针启用回调、策略模式和事件系统。分配 Op := Calc.Add 存储引用;调用 Op(10, 20) 在 Calc 实例上调用 Calc.Add。匿名方法(reference to function)是带闭包语义的现代替代方案。方法指针是 Delphi 事件驱动 VCL/FMX 架构的支柱。

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;

匿名方法与闭包

匿名方法(reference to function/procedure)是内联闭包,从封闭作用域捕获变量。'reference to' 类型是方法指针的现代替代方案 —— 它们按引用捕获变量,因此对捕获变量的更改会影响闭包。这启用了函数式模式:map/filter/reduce、回调和延迟执行。TFunc<T,TResult> 和 TProc<T> 是 System.SysUtils 中的泛型别名。匿名方法对并行编程(PPL)和现代 Delphi 惯用法至关重要。捕获的变量比其声明作用域寿命长。

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;

消息处理与 Windows 消息

VCL 基于 Windows 消息构建。'message' 指令处理特定消息(WM_LBUTTONDOWN、WM_KEYDOWN 等)。消息记录(TWMMouse、TWMKeyDown)是 TMessage 上的类型化覆盖。始终调用 inherited 让默认处理发生(除非你想抑制消息)。WndProc 在分派前拦截所有消息 —— 谨慎用于横切关注点。PostMessage 是异步的(立即返回);SendMessage 是同步的(等待处理器)。WM_USER + N 定义自定义消息。这是 Windows 事件驱动模型的基础。

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

应用事件与空闲处理

TApplicationEvents 集中应用级事件:OnIdle(消息队列为空时触发)、OnException(全局异常处理器)、OnMinimize/OnRestore、OnHint(状态栏提示)、OnMessage(所有 Windows 消息)。带 Done := False 的 OnIdle 创建紧密循环;谨慎使用。TTimer 以间隔触发 OnTimer(Interval 以毫秒为单位)—— 它基于消息,因此在阻塞操作期间不会触发。Application.ProcessMessages 在长时间操作期间泵送消息队列(防止"无响应")但可能导致重入 bug。TThread.Queue/Synchronize 将 UI 更新从后台线程封送到主线程。

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

使用 FireDAC 进行数据库访问

连接与查询基础

FireDAC 是 Delphi 的现代通用数据访问框架,支持 SQLite、PostgreSQL、MySQL、SQL Server、Oracle、InterBase 等。TFDConnection 管理数据库连接(设置 DriverName 和 Params)。TFDQuery 执行带参数的 SQL(:param 语法)—— 始终使用参数防止 SQL 注入。ExecSQL 运行 INSERT/UPDATE/DELETE/DDL(无结果集);Open 运行 SELECT(返回游标)。FieldByName('col').AsString/AsInteger 读取值。用 Next/Prev/First/Last 导航;Eof 标记结束。FireDAC 替代了旧版 dbExpress 和 BDE 技术。

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 打开整个表(SELECT * FROM tablename)—— 对简单 CRUD 方便但对大表不如 TFDQuery 高效。数据集导航:First/Next/Prior/Last/MoveBy。Locate 按字段值搜索(找到返回 True)。编辑:Append/Insert(新行)或 Edit(现有),然后设置字段,然后 Post(提交)或 Cancel(还原)。Filter 限制可见行(客户端)。IndexFieldNames 排序记录。将 TFDTable/TFDQuery 连接到 TDataSource,然后到 TDBGrid/TDBEdit 实现自动数据感知 UI。Live Bindings(FMX)提供控件到数据字段的可视化绑定。

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;

事务与批量操作

事务确保原子性 —— 所有操作成功或都不成功。StartTransaction/Commit/Rollback 包装相关操作。没有显式事务时,FireDAC 自动提交每条语句(对批量插入慢)。Array DML(Execute(count, startAt))在一次往返中发送参数化批次 —— 对批量插入显著更快(10-100 倍加速)。始终将事务包装在 try/except 中以便在失败时 Rollback。对于长事务,考虑隔离级别(xiReadCommitted、xiRepeatableRead)。连接池(TFDManager)提高多线程性能。

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;

存储过程与元数据

TFDStoredProc 调用数据库存储过程。设置 StoredProcName 和参数(ParamType:ptInput、ptOutput、ptInputOutput、ptResult)。ExecProc 运行不返回游标的过程;Open 运行返回结果集的过程。存储过程在服务器端封装业务逻辑以提高性能和安全性。TFDMetaInfoQuery 查询数据库架构(表、列、索引、约束)—— 适用于构建动态工具、ORM 或架构浏览器。MetaInfoKind 选项:mkTables、mkColumns、mkIndexes、mkPrimaryKey、mkForeignKeys。FireDAC 还支持架构缓存以进行离线元数据访问。

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 内存表与本地 SQL

TFDMemTable 是内存数据集 —— 非常适合缓存、临时数据和无需数据库的单元测试。用 FieldDefs 定义字段,然后 CreateDataSet。AppendRecord 添加行。支持索引、过滤器和所有数据集导航。本地 SQL(TFDLocalSQL)让你对任何 TDataSet 运行 SQL 查询(包括 TFDMemTable、TClientDataSet,甚至通过 ODBC 的 Excel)—— 启用内存表和数据库表之间的连接。这对 ETL、报表和构建离线工作的数据层非常强大。TFDMemTable 还可以加载/保存到二进制或 JSON 文件以持久化。

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

泛型与匿名方法

泛型类与方法

泛型(在 Delphi 2009 中引入)启用类型安全的容器和算法。TStack<T> 适用于任何类型 T —— 编译器生成专用版本。这消除了运行时转换(无 TObject 转换)并在编译时捕获类型错误。泛型类型参数使用 <T> 语法。支持泛型方法、类、记录和接口。约束(class、constructor、interface)限制可使用的类型。RTL 在 System.Generics.Collections 中提供 TList<T>、TDictionary<TKey,TValue>、TQueue<T>、TStack<T>、TObjectList<T>。

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;

泛型约束

泛型约束限制类型参数:'class'(必须是类类型)、'constructor'(必须有无参 Create 构造函数 —— 启用 T.Create)、'record'(必须是值类型)、接口名(必须实现该接口)。多个约束逗号分隔。约束启用在 T 上调用方法(例如,带 constructor 约束的 T.Create)。没有约束,你只能分配/比较 T(无方法调用)。类型推断有时让你省略显式类型参数。约束对构建类型安全框架和 ORM 至关重要。

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 提供类型安全的容器:TList<T>(动态数组)、TDictionary<TKey,TValue>(哈希映射)、TQueue<T>(FIFO)、TStack<T>(LIFO)、TObjectList<T>(拥有其对象 —— 自动释放它们)。Sort 使用默认比较;TComparer<T>.Construct 用匿名方法创建自定义比较器。FindIndex/基于谓词的搜索使用匿名函数谓词。TryGetValue 找到时返回 True 并输出值(避免异常)。AddOrSetValue 更新或插入。带 OwnsObjects := True 的 TObjectList<T> 在列表释放时自动释放包含的对象 —— 防止内存泄漏。

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;

作为回调的匿名方法

匿名方法在 Delphi 中启用函数式编程。'reference to function' 类型是闭包 —— 它们从封闭作用域捕获变量。Map 和 Filter 等高阶函数接受函数作为参数,实现简洁的数据转换。TFunc<T,TResult> 和 TProc<T> 是内置泛型委托类型。闭包按引用捕获变量,因此它们反映后续更改。此模式替代了冗长的回调接口,对 PPL(并行编程库)、事件处理器和 LINQ 风格操作至关重要。匿名方法是引用计数并自动管理的。

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;

泛型接口与 TComparer

泛型接口启用类型安全契约:IRepository<T> 适用于任何实体类型。与引用计数(TInterfacedObject)结合,这提供自动内存管理 —— 接口引用计数,最后一个引用释放时释放。TComparer<T>.Construct 从匿名比较函数创建 IComparer<T> —— 由 Sort、BinarySearch 和 SortedDictionary 使用。泛型接口是 Delphi 中依赖注入的基础(注册 IRepository<TUser>,注入到服务中)。Spring4D 框架用完整的 DI 容器扩展了这一点。泛型约束(class、constructor)确保 T 可以被实例化。

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 与反射

扩展 RTTI 基础

扩展 RTTI(运行时类型信息),在 Delphi 2010 中引入,提供完整反射:在运行时检查类型、属性、方法和字段。TRTTIContext 是入口点。GetType 返回类的 TRttiType。GetProperties 枚举 published 属性。GetValue/SetValue 使用 TValue(类似变体的类型)动态读/写属性值。默认只有 'published' 成员有 RTTI(使用 {$RTTI EXPLICIT ...} 指令获取更多)。RTTI 驱动序列化(JSON/XML)、ORM、依赖注入和可视化设计器。它有小的性能开销但启用强大的元编程。

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;

方法调用与特性

RTTI 可以通过 TRttiMethod.Invoke 动态调用方法 —— 将参数作为 TValue 数组传递。特性(TCustomAttribute 子类)使用 [Attribute] 语法将元数据附加到类型、属性和方法。GetAttributes 在运行时检索它们。这启用验证框架([Required]、[MaxLength])、ORM 映射([Table]、[Column])和序列化控制([JsonProperty])。特性是强大的元编程功能 —— 编译器将它们存储在 RTTI 中,框架读取它们以驱动行为。通过 RTTI 的方法调用比直接调用慢,但对脚本、DI 和动态分派至关重要。

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;

类型发现与枚举

TRTTIContext.GetTypes 枚举编译程序中所有带 RTTI 的类型 —— 适用于插件发现、ORM 模型扫描和构建类型浏览器。FindType 按限定名定位类型('UnitName.TypeName')。TRttiType 提供 GetFields(所有字段)、GetMethods(所有方法)、GetProperties(published 属性)。TypeKind 区分类、记录、接口、枚举等。AsInstance.MetaclassType 给出用于实例化的类引用。这启用自动发现并连接组件的框架。Spring4D 和 DORM 框架使用此进行自动 ORM 映射。RTTI 枚举很慢 —— 缓存结果以供重复使用。

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 与动态类型

TValue 是 Delphi 的动态值类型 —— 一个标记联合,持有任何类型及其类型信息。From<T> 包装值;AsType<T>/AsInteger/AsString 解包它。IsType<T> 检查类型。TryAsType 尝试安全转换。TValue 对 RTTI(属性值、方法参数)至关重要,并在静态类型语言中启用动态类型。它类似 C# 带类型信息的 'object',或 Python 的动态特性。TValue 处理原语、字符串、对象、数组和记录。在构建序列化器、脚本引擎或通用数据层时使用它。它有相比直接类型的开销但提供最大灵活性。

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;

使用 RTTI 序列化

RTTI 启用自动序列化 —— 将对象转换为/从 JSON、XML 或任何格式,无需手动映射代码。ObjectToJSON 迭代 published 属性,通过 RTTI 读取值,并构建 TJSONObject。JSONToObject 反转过程。此模式驱动 REST 客户端、配置系统和 ORM 层。REST.Json 单元提供 TJson.ObjectToJsonString 和 TJson.JsonToObject 开箱即用。对于生产使用,添加特性([JsonProperty('name')])控制字段名,并处理嵌套对象、数组和自定义类型。基于 RTTI 的序列化比手写映射器慢但更易维护。

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

接口与 COM

接口基础与引用计数

接口定义契约(方法签名)而无实现。TInterfacedObject 提供引用计数 —— 当最后一个接口引用释放时,对象自动释放(无需调用 Free)。这是 Delphi 对接口对象的自动内存管理。GUID(['{...}'])启用 COM 互操作和 InterfaceAs/Supports 检查。一个类可以实现多个接口(TShape 同时实现 IMovable 和 IDrawable)。接口属性是允许的(必须有 read/write 方法)。始终使用接口类型(IMovable)而非类类型(TShape)以使引用计数工作。混合对象和接口引用可能导致过早释放。

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;

使用接口的依赖注入

接口启用依赖注入 —— 通过构造函数传递依赖项(ILogger、IUserDataAccess)而非硬编码它们。这解耦 TUserService 与具体实现:无需更改 TUserService 即可将 TConsoleLogger 替换为 TFileLogger。TUserService 本身不引用计数(继承自 TObject,而非 TInterfacedObject),因此需要手动 Free。对于完整 DI,使用按接口类型解析依赖项的容器(Spring4D、DSharp):Container.RegisterType<ILogger, TConsoleLogger>; Container.Build; Service := Container.Resolve<TUserService>。DI 提高可测试性(注入模拟)、可维护性和模块性。始终依赖抽象(接口),而非具体实现。

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 互操作

COM(组件对象模型)让 Delphi 与 Windows 应用和库交互。CreateOleObject 通过后期绑定创建 COM 对象(Variant 类型 —— 无编译时检查,但简单)。导入类型库生成带类型化接口的早期绑定单元(IntelliSense、类型检查、更好性能)。IUnknown 是基础 COM 接口,带 AddRef/Release/QueryInterface 用于引用计数。stdcall 是 COM 调用约定。CoCreateInstance 是低级 API。常见 COM 用途:Office 自动化(Excel、Word)、ADO(数据库)、shell 集成、WMI 查询。在线程中 COM 操作前始终调用 CoInitialize。COM 对象是单元线程的 —— 谨慎在线程间封送。

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 与聚合

'implements' 指令将接口委托给属性 —— 组合优于继承。TDataService 通过委托给 FCache(TMemoryCache)公开 ICache。这比继承更清晰,让你混合匹配行为。Supports() 检查对象是否实现接口(内部使用 QueryInterface)。As 运算符执行检查的接口转换。接口委托启用装饰器模式(用日志记录包装缓存)、策略模式(交换缓存实现)和清晰的关注点分离。COM 的 QueryInterface 是底层机制 —— 每个接口对象都可以查询其支持的任何接口。

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;

弱引用与不安全引用

引用计数可能因循环引用(父↔子)导致内存泄漏。[Weak] 打破循环 —— 它跟踪引用但不增加引用计数,并在目标释放时自动置 nil。[Unsafe] 是原始指针(无跟踪,无引用计数)—— 最快但危险(悬空指针)。对父/反向引用、观察者模式和事件订阅使用 [Weak]。默认(强)引用增加引用计数并保持对象存活。Delphi 的 ARC(已弃用,改用 [Weak])曾经在移动设备上自动处理此问题。在桌面上,接口使用手动引用计数 —— [Weak] 对无循环设计至关重要。始终正确配对强引用和弱引用。

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

多线程与 PPL

TThread 基础

TThread 是 Delphi 多线程的基础。用后台工作重写 Execute。定期检查 Terminated 进行优雅取消。TThread.Synchronize 在主线程上执行代码(阻塞 —— 等待完成);TThread.Queue 是异步的(发布并立即返回)。切勿从后台线程访问 UI 控件 —— 始终使用 Synchronize 或 Queue。FreeOnTerminate := True 在 Execute 完成时自动释放线程。CreateAnonymousThread 从匿名方法创建一次性线程 —— 对简单任务方便。对于生产代码,优先使用 PPL(TTask)而非原始 TThread,以获得更好的组合和错误处理。

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;

并行编程库(PPL)

System.Threading 中的并行编程库(PPL)提供高级并发:TTask(即发即弃异步)、TTask.Future<T>(带返回值的异步)和并行循环。任务自动使用线程池 —— 无需管理线程。WaitForAll/WaitForAny 组合多个任务。Future.Value 阻塞直到结果就绪(类似 promise)。PPL 是原始 TThread 的现代替代方案 —— 更清晰、可组合,与 async/await 模式集成。任务捕获异常并在你访问 .Value 时重新引发它们,启用正确的错误传播。使用 TEvent/TCountdownEvent 进行任务间的细粒度同步。

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;

并行 For 与循环

TParallel.For 跨线程池并行化循环 —— 迭代在多个核心上并发运行。使用 &For(转义关键字),因为 'for' 是保留字。对于带独立迭代的 CPU 密集型循环,这可以在多核机器上提供接近线性的加速。关键:共享状态(如 Sum)必须用锁(TCriticalSection)保护或使用 TInterlocked.Increment 进行原子操作。State.Break 停止循环(类似 break)。State.ShouldExit 检查是否调用了 Break。避免并行化少量迭代或重 I/O 的循环(线程池耗尽)。Stride 控制迭代步进。嵌套并行循环很少有帮助 —— 外循环已经饱和核心。

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);

同步原语

System.SyncObjs 提供同步原语:TCriticalSection(互斥 —— 一次只有一个线程进入)、TEvent(线程间信号 —— SetEvent 唤醒,WaitFor 阻塞)、TMonitor(锁定任何对象 —— 类似 Java/C# 带有 Wait/Pulse 的监视器)、TInterlocked(原子 Increment/Decrement/Exchange/CompareExchange —— 无锁)。TCriticalSection 最常见 —— 始终将 Enter/Leave 与 try/finally 配对。TEvent.WaitFor 返回 wrSignaled、wrTimeout 或 wrAbandoned。TMonitor.Wait 临时释放锁并阻塞;Pulse/PulseAll 唤醒等待者。TInterlocked 对简单计数器最快 —— 无锁开销。选择正确的原语:CriticalSection 用于独占访问,Event 用于信号,Interlocked 用于原子计数器。

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);

线程池与 Async/Await 模式

TThreadPool 管理工作线程池 —— 重用线程避免创建开销。根据工作负载设置最小/最大线程(CPU 密集型:~核心数,I/O 密集型:更多)。TTask.Run 是 Create+Start 的简写。ContinueWith 链式任务 —— 在前驱完成后运行,启用管道。Task.Status(Created、WaitingToRun、Running、Completed、Canceled、Faulted)跟踪生命周期。ICancellation 启用协作式取消 —— 在长任务中定期检查 IsCancelled。对于真正的 async/await,Delphi 没有语言级 await,但 TTask.Future + .Value 提供等价语义。OmniThreadLibrary(OTL)在 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

使用 Indy 进行网络编程

TCP 客户端与服务器(Indy)

Indy(Internet Direct)是 Delphi 捆绑的网络库。TIdTCPClient 连接到服务器 —— WriteLn/ReadLn 用于基于行的协议,Write/Read 用于二进制。ConnectTimeout 防止挂起。TIdTCPServer 监听连接 —— OnExecute 在每个客户端的线程中运行(AContext 代表每个连接)。Indy 使用阻塞套接字(更简单的模型 —— 无回调),因此服务器处理器在工作线程中运行。始终优雅处理断开连接。对于高性能服务器,考虑 ICS(重叠 I/O)或 Synapse。Indy 组件是非可视的 —— 放在窗体上或在代码中创建。设置 Active := True 开始监听。DefaultPort 设置监听端口。

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 客户端(TIdHTTP)

TIdHTTP 是 Indy 的 HTTP 客户端 —— 支持 GET、POST、PUT、DELETE、头、cookie 和 SSL/TLS。对于 HTTPS,附加 TIdSSLIOHandlerSocketOpenSSL(需要 OpenSSL DLL:libeay32/ssleay32 或 libcrypto/libssl)。Request.ContentType 和 CustomHeaders 设置请求元数据。POST 接受字符串体(用于 JSON/API)或 TStrings(用于表单数据)。EIdHTTPProtocolException 捕获 HTTP 错误(404、500 等),带 ErrorCode 和 ErrorMessage。对于现代 REST 客户端,考虑 TRESTClient(内置,无 OpenSSL 依赖)或 TNetHTTPClient(更轻量)。始终在 finally 块中释放 HTTP 和 SSL 处理器。设置 Http.HandleRedirects := True 自动跟随 301/302 重定向。

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 电子邮件(TIdSMTP)

TIdSMTP 通过 SMTP 服务器发送电子邮件。TIdMessage 代表电子邮件(From、Recipients、Subject、Body)。对于 Gmail/Office365,使用 TLS(端口 587,utUseExplicitTLS)或 SSL(端口 465,utUseImplicitTLS)。Gmail 需要启用 2FA 的"应用密码"(非常规密码)。TIdAttachmentFile 添加文件附件。对于 HTML 电子邮件,设置 ContentType := 'text/html'。对于多部分(HTML + 纯文本 + 附件),使用 TIdMessageBuilderHTML。常见端口:25(未加密/中继)、465(SSL)、587(STARTTLS)。始终将 Connect/Send 包装在 try/finally 中以确保 Disconnect。对于接收电子邮件,使用 TIdPOP3 或 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 与原始套接字

UDP 是无连接的 —— 无握手,无保证交付,但比 TCP 快。TIdUDPClient.Send 发送数据报;ReceiveString 等待带超时的响应。BroadcastEnabled 发送到 255.255.255.255(LAN 上的所有设备)—— 适用于服务发现。TIdUDPServer.OnUDPRead 接收数据报;ABinding.PeerIP/PeerPort 标识发送者。UDP 非常适合:DNS、SNMP、游戏状态更新、流媒体和发现协议。对于 UDP 上的可靠性,在应用层实现 ACK/重试。TIdBytes 是 Indy 的字节数组类型 —— 使用 BytesToString/ToBytes 进行转换。对于原始套接字控制(原始 IP 数据包、自定义协议),使用 WinSock2 单元或 Synapse 库。

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 客户端

TIdFTP 提供 FTP 客户端功能 —— Connect、List、Put(上传)、Get(下载)、MakeDir、ChangeDir。被动模式(Passive := True)通过 NAT/防火墙工作。UseTLS 保护 FTP(FTPS)。对于 SFTP(基于 SSH),使用第三方库(libssh2、SecureBlackbox)。TRESTClient/TRESTRequest/TRESTResponse 是内置 REST 组件(无 OpenSSL 依赖)—— 非常适合现代 API 消费。Resource 使用由 AddUrlSegment 填充的 {param} 占位符。Execute 发送请求;RESTResponse.Content 持有正文;JSONValue 自动解析 JSON。REST 组件支持 OAuth2、基本认证和自定义认证器。对于高性能 REST,考虑 TNetHTTPClient(更轻)或 Indy 的 TIdHTTP 获得最大控制。

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 包

创建与使用 DLL

DLL(动态链接库)跨应用共享代码。使用 'library' 关键字(而非 'program')构建 DLL。'exports' 列出对外部调用者可用的函数。stdcall 是标准 Windows 调用约定(C/C++、VB、C# 兼容)。静态导入(external)在编译时链接 —— DLL 必须在运行时存在。动态加载(LoadLibrary/GetProcAddress)在运行时加载 —— 启用插件和可选功能。FreeLibrary 卸载 DLL。PChar(PWideChar)是 DLL 导出的标准字符串类型(共享内存,无 Delphi 特定类型)。切勿直接导出 Delphi 字符串、对象或接口 —— 它们是 Delphi 内部的。使用 ShareMem 单元进行 Delphi 到 Delphi 的字符串共享(需要 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;

通过接口共享对象

跨 DLL 边界共享对象很棘手 —— Delphi 类不能直接导出(不同的内存管理器,不同的 RTTI)。解决方案:使用带 GUID 的接口。DLL 导出返回 IPlugin 的工厂函数(CreatePlugin)。宿主应用定义相同的接口(相同的 GUID!)并调用工厂。接口引用计数自动处理清理。对字符串使用 PChar(而非 Delphi string)以避免内存管理器冲突。这是插件架构模式 —— 动态加载 DLL,通过工厂创建插件,通过接口通信。对于完整插件系统,考虑 Delphi 中的插件框架或使用包(BPL),它们共享 RTL 并允许直接类共享。

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 包(Borland 包库)

BPL(Borland 包库)是 Delphi 特定的共享库 —— 它们共享 Delphi RTL,允许直接类/对象共享(不同于 DLL)。用 'package' 关键字构建。运行时包减少 EXE 大小(共享代码在 .bpl 文件中)并启用热插拔模块。LoadPackage/UnloadPackage 动态加载 BPL —— GetClass 按名称查找注册的类。RegisterClass/UnRegisterClass 使类可发现。BPL 需要部署 Delphi RTL BPL(rtl.bpl、vcl.bpl)。使用 BPL 用于:插件架构(直接共享 Delphi 类型)、模块化应用(按需加载功能)和减少内存(共享代码加载一次)。对于跨语言共享,使用 DLL;对于仅 Delphi,BPL 更强大。

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.

跨边界内存管理

头号 DLL 陷阱:在一个模块中释放在另一个模块中分配的内存。每个模块有自己的内存管理器 —— 混合它们导致堆损坏和崩溃。解决方案:(1) ShareMem —— 共享 BorlndMM.dll,但需要部署该 DLL。(2) 调用者分配模式 —— 调用者提供缓冲区,DLL 填充它(最安全,语言无关)。(3) SimpleShareMem/FastMM —— 现代共享内存管理器(FastMM 自 Delphi 2006 起为默认)。(4) 基于回调的释放 —— DLL 提供释放函数。对于 PChar 返回,使用 StrNew/StrDispose(Windows API,共享)。对于生产 Delphi 到 Delphi,使用 BPL(共享 RTL)或 SimpleShareMem。对于跨语言,始终使用调用者分配模式。切勿在没有共享内存管理器的情况下跨 DLL 边界传递 Delphi string/object/interface 类型。

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;

资源文件与嵌入

资源文件将二进制数据(图像、图标、声音、字符串、版本信息)嵌入 EXE/DLL —— 无需外部文件。创建 .rc 脚本,用 brcc32 编译(或让 IDE 自动编译)。{$R file.res} 链接它。TResourceStream 将 RCDATA 资源作为流读取。LoadIcon/LoadString 使用 Windows API 处理特定资源类型。资源在运行时是只读的,但将所有内容保存在一个文件中(非常适合部署)。常见用途:应用图标、启动画面图像、默认配置、WAV 声音、版本信息(文件属性对话框)、本地化字符串。对于大数据,考虑嵌入前压缩。资源 ID 可以是名称(字符串)或数字。RT_RCDATA 是通用二进制资源类型。

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

调试与性能调优

调试器与断点

Delphi 的 IDE 调试器很强大:通过点击装订线设置断点。条件断点仅在表达式为真时中断(例如,i > 100)。日志/跟踪断点记录消息而不停止 —— 非常适合监视循环。asm int 3 end 在代码中创建硬断点(CPU 陷阱)。OutputDebugString 记录到事件日志窗口(和 DebugView 工具)。Assert 在调试构建中检查条件(用 {$C-} 或在发布中关闭断言禁用)。DebugHook 在 IDE 中运行时非零。调用堆栈窗口跟踪调用链;线程窗口检查所有线程;局部变量显示当前作用域。启用"使用调试 DCU"以单步执行 RTL/VCL 源代码。

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

异常处理与堆栈跟踪

Delphi 异常:try/except 捕获错误,try/finally 保证清理。异常类形成层次结构:Exception → EDivByZero、EAccessViolation、EListError、EAbort(静默)等。'on E: ExceptionType do' 捕获特定类型;基础 'on E: Exception do' 捕获所有。'raise;' 重新引发当前异常(保留堆栈跟踪)。EAbort(或 Abort 过程)引发静默异常(无对话框)。TApplicationEvents.OnException 是全局处理器 —— 捕获未处理的异常。对于堆栈跟踪,使用 JCL(JclDebug)或 MadExcept/ExceptionHunter —— 它们捕获调用堆栈、寄存器转储,甚至电子邮件崩溃报告。始终记录异常以便事后调试。切勿在生产中静默吞掉异常。

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

性能分析与性能

TStopwatch 是高精度计时器(使用 QueryPerformanceCounter)。优化前始终进行基准测试 —— 不要猜测。ReportMemoryLeaksOnShutdown := True 在程序退出时捕获泄漏(调试构建)。常见 Delphi 性能陷阱:(1) 循环中的字符串连接创建副本 —— 使用 TStringBuilder 或预分配。(2) 循环中的 SetLength 重新分配 —— 一次设置大小。(3) 按值传递字符串/数组复制它们 —— 对只读参数使用 'const'。(4) TStringList.Sorted + Find 是 O(log n);未排序的 IndexOf 是 O(n)。(5) TList<T>.Add 是摊销 O(1) 但前面 Insert 是 O(n)。对于深度分析,使用 Sampling Profiler(免费)、AQTime 或 GpProfile —— 它们无需代码更改即可识别热点。优化占用 80% 时间的 20% 代码。

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

内存管理与泄漏

内存管理是 Delphi 最大的 bug 来源。规则 #1:每个 Create 必须有匹配的 Free。虔诚地使用 try/finally。对于自动管理,使用接口(TInterfacedObject + 引用计数)—— 无需 Free。带 OwnsObjects := True 的 TObjectList<T> 自动释放包含的对象。ReportMemoryLeaksOnShutdown := True 在退出时显示列出泄漏对象的对话框(仅调试)。FullDebugMode 中的 FastMM(默认内存管理器)将泄漏记录到文件,带分配堆栈跟踪 —— 对跟踪泄漏至关重要。常见泄漏模式:缺少 try/finally、未移除的事件处理器、循环引用(用 [Weak] 修复)、未释放的线程、finalization 中未释放的全局对象。单元的 finalization 部分在关闭时运行 —— 用于全局清理。

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

代码质量与测试

DUnitX 是现代单元测试框架(替代 DUnit)。[TestFixture] 标记测试类,[Test] 标记测试方法,[Setup]/[TearDown] 在每个测试前后运行。[TestCase] 用内联数据参数化测试。Assert.AreEqual/IsTrue/WillRaise 验证结果。测试驱动开发(TDD):先写测试,再写代码。测试捕获回归并记录预期行为。Delphi Mocks(或 Spring4D 模拟)从接口创建模拟对象 —— Setup.Expect 定义预期,VerifyAll 检查它们是否被满足。模拟对隔离单元(模拟数据库、网络、文件系统)至关重要。目标是业务逻辑的高覆盖率。在 CI(持续集成)中运行测试以尽早捕获回归。集成测试验证组件协同工作;单元测试隔离验证单个单元。

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

泛型与集合

泛型类声明

泛型让你编写类型安全的容器而无需转换。在类型名后用 <T> 声明。编译器为使用的每个类型生成专用版本。在泛型类型中使用 TArray<T> 代替 array of 作为动态数组。

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 用法

TDictionary<K,V> 是泛型哈希映射。Add 在键重复时引发异常;OrAdd 执行 upsert。TryGetValue 在键不存在时返回 false(而非异常)。始终释放字典 —— 默认它们不拥有对象。

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

TList<T>.Sort 使用 IComparer<T>。TComparer<T>.Construct 将匿名函数包装为比较器。BinarySearch 要求列表已用相同比较器排序。AddRange 接受开放数组或另一个列表。

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;

泛型约束

约束限制可替换的类型:'class'(引用类型)、'record'(值类型)、'constructor'(无参构造函数)或特定祖先类。多个约束用逗号分隔。没有 'constructor' 你无法调用 T.Create。

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;

用 TObjectDictionary 实现对象所有权

TObjectDictionary<K,V> 扩展 TDictionary 增加所有权。传递 [doOwnsValues]、[doOwnsKeys] 或两者。在 Remove/Clear/Free 时,拥有的对象自动释放 —— 防止对象集合中的内存泄漏。

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

匿名方法与闭包

基本匿名方法

匿名方法是内联函数引用。TFunc<...> 用于函数,TProc<...> 用于过程。它们从封闭作用域捕获变量(闭包)。可赋值给变量,可作为参数传递。

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;

闭包捕获变量

捕获的变量在堆上分配,生命周期与匿名方法一样长。每次调用 MakeMultiplier 捕获自己的 Factor —— 闭包是独立的。这就是工厂和部分应用的工作方式。

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;

高阶函数

'reference to' 声明与匿名方法兼容的过程类型。Apply 是高阶函数 —— 接受函数作为参数。这实现了 map/filter/reduce 模式。使用 TArray<Integer> 作为动态数组。

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;

带闭包的事件处理器

匿名方法可以替代传统的基于方法的事件处理器,捕获上下文而无需字段。适用于一次性处理器和减少样板代码。捕获的 Caption 与 OnClick 持有的闭包引用一起存活。

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

CreateAnonymousThread 将闭包包装在线程中 —— 即发即忘的后台工作。使用 TThread.Queue(或 Synchronize)将 UI 更新封送回主线程。切勿从工作线程直接操作 UI 控件。

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

特性与 RTTI

自定义特性声明

特性是继承 TCustomAttribute 的类。用 [AttrName(...)] 应用于类型、字段、方法、属性。编译器将它们嵌入 RTTI。构造函数参数成为特性参数。

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;

通过 RTTI 读取特性

TRttiContext 是 RTTI 的入口点。GetType 返回类的 TRttiType。GetAttributes 返回所有应用的特性。转换到你的特性类型以读取属性。RTTI 要求类在用 {$M+} 编译的单元中或派生自 TPersistent。

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;

字段和方法 RTTI

GetFields 返回所有 public/published 字段。SetValue/GetValue 提供按名称的动态字段访问 —— 对序列化器和 ORM 很有用。GetMethods 返回所有方法(包括继承的)。RTTI 比直接调用慢。

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;

属性 RTTI 和调用

GetProperties 返回 published 属性。IsReadable/IsWritable 检查访问器。GetValue/SetValue 也适用于属性。TypeKind(tkInteger、tkString、tkClass 等)让你适当处理每种类型。这是大多数 Delphi 序列化器的工作方式。

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;

按名称调用方法

GetMethod 按名称查找方法(区分大小写)。Invoke 用 TValue 数组参数动态调用它。TValue 是任何类型的类似变体的包装器。适用于插件系统、脚本和后期绑定。返回 TValue —— 用 AsInteger、AsString 等转换。

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

接口深入

接口声明与实现

接口定义无实现的契约。GUID(可选但推荐)启用 'as' 转换和 Supports()。TInterfacedObject 提供引用计数。所有接口方法必须实现(没有 'abstract' 逃避)。接口中的属性需要访问器方法。

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;

引用计数与内存

接口引用是引用计数的。当最后一个接口引用离开作用域时,对象被释放。切勿混合对同一实例的对象和接口引用 —— 接口引用计数会在对象指针仍指向它时释放它。选择一种所有权模型。

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;

接口继承与多接口

接口可以从多个父接口继承。一个类可以实现多个接口。方法解析子句(method = interface.method)在多个接口声明相同方法时解决冲突。使用 'as' 或 Supports() 在运行时查询接口。

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 和 as 转换

Supports() 检查对象是否实现接口 —— 返回布尔值,可选返回接口。'as' 转换做同样的事但在失败时引发 EInvalidCast。Supports() 适用于对象和接口引用。要求接口有 GUID。

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;

依赖注入模式

将依赖项作为接口传递 —— 启用模拟、替换实现和可测试性。类依赖抽象(ILogger),而非具体类型。这是 Spring4D 等 DI 容器的基础。接口所有权意味着记录器存活时间与服务持有引用一样长。

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

内存管理进阶

try-finally 模式

始终在 try-finally 中将分配与 Free 配对。为多个资源嵌套 finally 块。FreeAndNil(代替 Free)还清除变量 —— 对检测 use-after-free 很有用。Free 对 nil 安全 —— 无需先检查 Assigned。

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;

基于接口的所有权

TInterfacedObject + 接口引用 = 自动清理。当接口离开作用域时,析构函数运行。这是 Delphi 中的 RAII —— 将资源包装在接口对象中实现保证清理而无需 try-finally 样板代码。

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)

弱引用

弱引用打破引用循环。没有 [Weak],两个互相持有接口引用的对象将永远不会被释放(循环)。TComponent 有内置的 FreeNotification 机制用于弱引用。[Weak] 特性需要 RTTI 并适用于接口和类字段。

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;

记录与对象

记录是值类型(栈,赋值时复制)—— 无需内存管理。类是引用类型(堆,必须释放)。对小的不可变数据(点、日期、金额)使用记录。对多态或大对象使用类。现代 Delphi 中记录可以有方法和运算符。

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;

内存泄漏检测

ReportMemoryLeaksOnShutdown 在退出时显示列出泄漏对象的对话框。FastMM(默认内存管理器)检测泄漏、双重释放和 use-after-free。对于生产,将泄漏记录到文件。在开发期间定期运行泄漏检查 —— 在引入时修复泄漏更容易。

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)

跨平台窗体基础

FMX 窗体是跨平台的(Windows、macOS、iOS、Android、Linux)。相同代码,不同的原生渲染器。使用 FMX.* 单元代替 Vcl.*。控件是基于矢量的(完美缩放)。样式取代主题 —— 视觉外观是数据驱动的。

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.

布局与对齐

FMX 使用 Align(Client、Top、Bottom、Left、Right、None)和 Margins/Padding 进行布局。TFlowLayout 像 CSS flexbox 一样排列子控件。TGridLayout 创建网格。使用 TScaleBox 实现分辨率无关的缩放。布局本身是控件 —— 可嵌套。

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;

样式与样式化

样式是存储在 .fsf 或 .style 文件中的视觉资源(画刷、字体、效果)集合。StyleLookup 为控件选择命名样式。TStyleManager 在运行时切换全局样式。FMX 样式是矢量的 —— 缩放到任何 DPI。样式设计器可视化编辑样式。

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

效果与动画

效果(Glow、Shadow、Blur、Reflection)是父级为控件的非可视组件。动画(TFloatAnimation、TColorAnimation、TPathAnimation)随时间动画化属性。将 Parent 设置为目标控件。Trigger/Start 开始。全部 GPU 加速 —— 在所有平台上平滑。

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;

平台服务

平台服务抽象 OS 特定功能。用 SupportsPlatformService 查询 —— 在不支持的平台上返回 false。使用前始终检查。常见服务:剪贴板、对话框、虚拟键盘、设备信息、屏幕。此模式使你的代码跨平台而无需 {$IFDEF} 块。

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

数据库(FireDAC)

连接设置

TFDConnection 是中央 FireDAC 对象。设置 DriverName(SQLite、MSSQL、MySQL、PostgreSQL、Oracle 等)和 Params。连接定义可存储在 .ini 文件中以便重用。释放前始终设置 Connected := False。使用 TFDManager 进行连接池。

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;

查询执行

对 SELECT 使用 Open(返回游标),对 INSERT/UPDATE/DELETE 使用 ExecSQL(返回受影响行数)。始终使用参数 —— 切勿将值连接到 SQL 中(注入风险)。ParamByName 不区分大小写。FieldByName 按名称访问列。Eof/Next 迭代行。

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;

事务

StartTransaction/Commit/Rollback 包装原子操作。如果任何语句失败,Rollback 撤销所有更改。嵌套事务使用保存点(部分回滚)。始终包装在 try-except-raise 中以在回滚后传播错误。没有事务时,每个语句自动提交。

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 与实时数据

TFDTable 是表上的实时、可编辑游标。Edit/Post 修改当前行。Append/Post 插入。Delete 移除当前行。更改直接进入数据库。使用 IndexFieldNames 进行排序。对于复杂查询,改用 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;

批量更新与缓存模式

CachedUpdates 模式在内存中缓冲更改 —— 用 ApplyUpdates 一次性应用。比逐行更新批量操作更快。CancelUpdates 丢弃缓冲。Status 显示每行的更改类型。适用于断开连接的场景和减少往返。

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 基础

TRESTClient 持有基础 URL。TRESTRequest 构建请求(方法、资源、参数)。TRESTResponse 持有结果。URL 段({id})由 AddUrlSegment 替换。StatusCode/Content 提供 HTTP 响应。按创建的相反顺序释放。

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 解析

System.JSON 提供 TJSONObject、TJSONArray、TJSONValue。ParseJSONValue 解析字符串(返回 TJSONValue —— 按需转换)。GetValue<T> 读取类型化值。AddPair/AddElement 构建 JSON。所有 JSON 对象必须释放 —— 仅当由父级拥有时才引用计数。

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;

带 DataSnap 的 REST 服务器

DataSnap 自动将 Delphi 方法公开为 REST 端点。方法名成为 URL 段。参数映射到 URL 段或 POST 正文。TJSONObject/TJSONArray 是标准返回类型。应用如 [httppost] 的特性以指定 HTTP 动词。使用 TDSServerModule 作为基类。

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

TIdHTTP(Indy)提供对 HTTP 的完全控制 —— 头、cookie、重定向、超时。比 TRESTClient 更冗长但更灵活。对于 HTTPS,分配 SSL IOHandler(TIdSSLIOHandlerSocketOpenSSL)。为生产设置 ReadTimeout/ConnectTimeout。Indy 是同步的 —— 包装在 TThread 中以实现异步。

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;

带任务的异步 HTTP

将 REST 调用包装在 TTask.Run 中以避免阻塞 UI 线程。用 TThread.Queue(异步)或 TThread.Synchronize(同步)封送 UI 更新回主线程。注意对象生命周期 —— 请求必须比任务存活更长。考虑 TRESTRequest.ExecuteAsync 获得内置异步支持。

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

多线程(并行)

TThread 基础

子类化 TThread 并重写 Execute。Create(False) 立即启动;Create(True) 需要 .Start。FreeOnTerminate := True 自动释放 —— 切勿对此类线程调用 Free。定期检查 Terminated 以优雅关闭。切勿从 Execute 操作 UI —— 使用 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 与 future

来自 System.Threading 的 ITask/IFuture<T> 比 TThread 更高级。Future 返回类型化值 —— .Value 阻塞直到结果就绪。任务是引用计数的(无需手动 Free)。使用 TTask.WaitForAll / WaitForAny 协调多个任务。比原始 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;

并行 for 循环

TParallel.For 跨 CPU 核心并行运行循环迭代。必须同步共享状态(使用 TCriticalSection 或 TInterlocked)。迭代顺序是非确定性的。使用 TLoopState 实现 break/continue。对 CPU 密集型工作更快;对琐碎迭代因开销而更慢。

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;

同步原语

TCriticalSection:互斥(一次只有一个线程)。TEvent:线程间信号(SetEvent/WaitFor)。带手动复位的 TEvent 保持信号直到 Reset。TInterlocked.Increment 是原子的,比关键区对简单计数器更快。TMonitor(内置于 TObject)是另一个选项。

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 与 Synchronize

UI 控件只能从主线程操作。Synchronize 阻塞工作线程直到主线程执行匿名方法 —— 谨慎使用(导致串行化)。Queue 投递后继续 —— 适用于即发即忘的 UI 更新。传递 nil 作为线程参数以使用当前线程。

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

包与组件

包项目基础

包(.bpl)是带 Delphi 元数据的 DLL —— 在应用间共享代码。'requires' 列出依赖。'contains' 列出此包中的单元。设计时包将组件安装到 IDE 中;运行时包随应用发布。拆分设计/运行时以减少 IDE 膨胀。

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)

自定义组件骨架

从最接近的现有类派生(TCustomLabel 提供没有 published 属性的标签)。仅重新发布你想要暴露的属性。Register 过程将组件添加到 IDE 面板。'default' 设置初始值(必须与构造函数匹配)。将 Register 放在设计时包中。

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;

组件属性与编辑器

TComponent 是非可视组件的基类。拥有子对象(FItems)—— 在构造函数中创建,在析构函数中释放。TStrings 属性获得内置字符串编辑器。RegisterPropertyEditor 为特定属性自定义对象检查器。对需要流式传输的嵌套对象使用 TPersistent。

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);

事件与方法指针

事件类型是带 'of object' 的过程类型 —— 它们同时持有对象引用和方法指针。调用前始终检查 Assigned() —— nil 事件引发 AV。Do* 方法(DoChange、DoClick)是触发事件的受保护分派器。子类可以重写 Do* 以拦截事件。

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.

流式传输与持久化

TPersistent 启用流式传输和 Assign。Published 属性自动保存到 DFM 文件。重写 Assign 以支持对象间复制。DefineProperties 将非 published 数据添加到流。WriteComponent/ReadComponent 序列化到任何 TStream。这是窗体持久化其状态的方式。

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;

这篇内容对您有帮助吗?