Структура программы и основы
Структура программы и модули
Программа Delphi начинается с 'program' и заканчивается 'end.' (с точкой). {$APPTYPE CONSOLE} — директива компилятора, помечающая приложение как консольное. 'uses' импортирует модули — System.SysUtils содержит Format, IntToStr и т. д. Модули имеют секцию interface (публичные объявления) и implementation (код). WriteLn выводит текст с переносом строки; Write — без. ReadLn читает ввод (или приостанавливает). Главный блок begin..end — точка входа программы.
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 (80-битный с плавающей точкой на x86), 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 (с плавающей точкой).
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. Различие := и = — источник ошибок №1 у новичков.
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() — это sprintf в Delphi — использует %s (строка), %d (целое), %f (с плавающей точкой), %x (hex), %m (валюта), с модификаторами ширины/точности. WriteLn(value:width:decimals) форматирует числа напрямую. ReadLn читает ввод в переменную. StrToInt/StrToFloat преобразуют строки в числа (выбрасывают EConvertError при неудаче); TryStrToInt возвращает Boolean и безопаснее. IntToStr/FloatToStr преобразуют числа в строки. FormatDateTime форматирует даты (yyyy, mm, dd, hh, nn, ss). FloatToStrF даёт точный контроль (ffFixed, ffCurrency, ffExponent).
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.
// 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.Поток управления
If...Then...Else
If...Then...Else — условная конструкция Delphi. КРИТИЧНО: нет точки с запятой перед 'else' — точка с запятой завершает оператор, а else — часть if. Для ветвей с несколькими операторами оборачивайте в begin..end (всё равно без точки с запятой перед else). and/or/not — логические операторы (также побитовые для целых). Используйте скобки для группировки условий: (A > 0) and (B > 0). Правило отсутствия точки с запятой перед else — самая частая синтаксическая ошибка 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 (переключатель)
Case — переключатель Delphi — работает с порядковыми типами (Integer, Char, перечисление, диапазон). Каждая ветвь может быть одним значением, списком через запятую ('D', 'F') или диапазоном (1..5). Предложение else — значение по умолчанию. Case НЕ проваливается (в отличие от C). Для ветвей с несколькими операторами используйте begin..end. Case чище, чем цепочка if-else, для дискретных значений. Нельзя использовать case для строк напрямую (используйте if-else или поиск).
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 предпочтителен для коллекций (чище, без ошибок индекса).
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 продолжается, пока условие ИСТИННО; repeat останавливается, когда условие ИСТИННО (противоположная логика!). repeat...until не требует begin..end (это по сути блок). Используйте while для 'ноль или более раз' и repeat для 'один или более раз'. Break выходит; Continue переходит к проверке. while True с Break — распространённый шаблон для циклов со сложными условиями выхода.
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 может сделать код менее читаемым при чрезмерном использовании — используйте экономно для простых случаев.
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;Строки и обработка текста
Типы строк и операции
Строка по умолчанию в Delphi — UnicodeString (UTF-16, с подсчётом ссылок, копирование при записи). Строки индексируются с 1 (S[1] — первый символ) — частый источник багов для C-программистов. Length() возвращает количество символов. Pos() ищет подстроку (возвращает 0, если не найдено, а не -1). Copy() извлекает подстроку (Start, Count). StringReplace заменяет (rfReplaceAll для всех вхождений). Trim/TrimLeft/TrimRight удаляют пробелы. Split/Join — современные методы (TArray<string>). Используйте SameText для сравнения без учёта регистра.
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() — sprintf в Delphi: %d (целое), %f (с плавающей точкой), %s (строка), %x (hex), %m (валюта), с модификаторами ширины/точности. FloatToStrF даёт точный контроль (ffFixed, ffCurrency, ffNumber, ffExponent). FormatDateTime форматирует даты: yyyy (4-значный год), mm (месяц), dd (день), hh (час), nn (минута), ss (секунда), dddd (полное имя дня), mmmm (полное имя месяца). StrToInt/StrToFloat выбрасывают EConvertError при некорректном вводе; TryStrToInt возвращает Boolean (безопаснее). Всегда используйте Try... для пользовательского ввода.
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.
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 и кодировка
Char — 2-байтовый символ Unicode. Ord() возвращает кодовую точку; Char() преобразует обратно. IsDigit/IsLetter/IsWhiteSpace/IsUpper/IsLower классифицируют символы. ToUpper/ToLower преобразуют регистр. TEncoding.UTF8.GetBytes преобразует строки в массивы байтов (важно для файлового ввода-вывода и работы с сетью) — UTF-8 использует 1-4 байта на символ. TEncoding.Unicode — UTF-16 LE (всегда 2 байта/символ). Base64 (TNetEncoding.Base64) кодирует бинарные данные как текст для передачи. String и Char внутренне UTF-16; преобразуйте в UTF-8 для файлового хранения и сетевых протоколов.
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 захватывают части круглыми скобками — обращайтесь через Groups[1], Groups[2] (индексация с 1). Replace подставляет совпадения ($1, $2 ссылаются на группы). Split разбивает по шаблону. Распространённые регулярные выражения: \d (цифра), \w (символ слова), \s (пробел), + (один и более), * (ноль и более), {n} (ровно n), ^/$ (начало/конец). roCompiled компилирует для более быстрого повторного использования. Всегда проверяйте пользовательский ввод (email, телефоны) регулярными выражениями.
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;Массивы, записи и коллекции
Статические и динамические массивы
Статические массивы имеют фиксированный размер, заданный во время компиляции с пользовательским диапазоном индексов (array[0..4] или array[1..7]). Динамические массивы (array of T) изменяемого размера через SetLength — они индексируются с 0 и имеют подсчёт ссылок. High() возвращает последний индекс; Length() возвращает количество. SetLength для существующего динамического массива изменяет его размер (сохраняя существующие значения при увеличении). Установите в nil для освобождения. Литералы динамических массивов используют [1, 2, 3]. Многомерные динамические массивы — это 'массивы массивов' (рваные) — каждая строка может иметь разную длину.
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) создают объединение, где поля разделяют память — полезно для тегов типов. Используйте записи для небольших лёгких данных (точки, координаты, конфигурация). Используйте классы для больших объектов, требующих наследования или полиморфизма. Записи быстрее (нет выделения в куче), но не могут быть унаследованы.
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), set of Char для проверки (['0'..'9']), дни недели. Перечисления — порядковые типы — итерируйте от Low() до High(), преобразуйте в строку через GetEnumName. Множества делают комбинации флагов элегантными и типобезопасными.
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 (это объекты, а не записи).
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) в среднем).
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;Процедуры, функции и параметры
Процедуры и функции
Процедуры (без возвращаемого значения) и функции (возвращают значение) — подпрограммы Delphi. Переменная Result — возвращаемое значение — присваивайте ей (функция возвращает значение по завершении). Exit() возвращает немедленно со значением (современный синтаксис). Предварительные объявления позволяют вызвать функцию до определения её тела (полезно для взаимной рекурсии). Функции могут возвращать любой тип, включая записи, массивы и объекты. Exit без значения просто выходит из процедуры. Переменная Result объявлена неявно и соответствует возвращаемому типу.
// 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: передача по ссылке (изменяет переменную вызывающей стороны — как ref в C#). out: только для вывода (вызывающая сторона не инициализирует; функция устанавливает). Параметры по умолчанию должны быть последними. Параметры открытого массива (array of T) принимают любой массив или литерал [1,2,3] — используйте const для эффективности. const предпочтителен для строк и массивов (без копирования); используйте var только когда нужно изменить значение вызывающей стороны. Открытые массивы индексируются с 0 независимо от границ исходного массива.
// 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). Параметры по умолчанию — альтернатива — вызывающие могут их опускать. Предпочитайте перегрузку, когда логика различается по типу; используйте значения по умолчанию для необязательных значений. Неоднозначность (две перегрузки, соответствующие одинаково) — ошибка компиляции. Перегрузки должны различаться количеством или типами параметров (только возвращаемого типа недостаточно).
// 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). Они необходимы для сортировки дженериков, обработчиков событий и обратных вызовов. Захваченные переменные размещаются в куче (они переживают окружающую функцию).
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;Рекурсия и вспомогательные подпрограммы
Рекурсия — функция, вызывающая саму себя — требует базового случая для завершения. Факториал и Фибоначчи — классические примеры. Хвостовая рекурсия (где рекурсивный вызов — последняя операция) может быть оптимизирована компилятором. Вложенные процедуры/функции объявляются внутри другой подпрограммы и могут обращаться к её переменным (лексическая область видимости) — полезно для помощников, не нуждающихся в видимости снаружи. Остерегайтесь переполнения стека при глубокой рекурсии (используйте итерацию для больших входных данных). Мемоизация (кэширование результатов) может ускорить рекурсивные алгоритмы вроде Фибоначчи.
// 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;Классы и ООП
Определение класса, конструктор и деструктор
Классы — ссылочные типы (в куче, доступны через указатели). Create — конструктор; Destroy — деструктор (всегда переопределяйте; вызывается через Free). 'inherited' вызывает метод базового класса. Поля используют префикс F по соглашению. Свойства (property X: Type read GetX write SetX) обеспечивают контролируемый доступ — вызывающие используют P.Age, но сеттер проверяет. Видимость: private (только модуль в старом Delphi; strict private по-настоящему приватный), protected (подклассы), public (все), published (RTTI, для форм/инспекторов). Всегда оборачивайте создание объекта в try..finally, чтобы гарантировать вызов Free.
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;Свойства и индексированные свойства
Свойства инкапсулируют доступ к полям через геттеры/сеттеры. Свойства только для чтения имеют только спецификатор 'read'. Директива 'default' делает индексированное свойство по умолчанию — так L[i] работает вместо L.Items[i]. Свойства могут иметь прямой доступ к полю (read FCount) или доступ через метод (read GetItem write SetItem) для проверки/вычисления. Индексированные свойства обеспечивают синтаксис, похожий на массив. Опубликованные свойства (секция published) видны для RTTI и дизайнера форм. Свойства — способ 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' заменяет его в подклассе. Во время выполнения вызывается метод ФАКТИЧЕСКОГО объекта (виртуальная диспетчеризация) — вызов Speak по ссылке TAnimal, содержащей TDog, вызывает TDog.Speak. Статические методы (Move) определяются типом переменной, а не объекта. 'inherited' вызывает базовый метод. Конструкторы могут быть виртуальными (шаблон фабрики). Используйте virtual/override для полиморфизма; статические методы, когда поведение фиксировано. Всегда освобождайте созданные объекты.
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) разделяются всеми экземплярами. Шаблон Template Method: TShape.Describe вызывает абстрактные Area/Perimeter, которые заполняются подклассами. Абстрактные методы определяют 'что'; подклассы определяют 'как'.
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' приводит к интерфейсу (выбрасывает исключение, если не поддерживается). Интерфейсы — основа поддержки 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;Исключения и обработка ошибок
Try...Except...Finally
try...except перехватывает исключения (как try/catch в C#). Каждое 'on E: ExceptionType do' обрабатывает специфичное исключение. try...finally гарантирует выполнение очистки независимо от исключений (без обработки исключений — используйте для вызовов Free). Шаблон — try...try...except...finally (внутренний except для обработки, внешний finally для очистки). 'raise' (без аргументов) повторно выбрасывает текущее исключение. Exception — базовый класс; EFileNotFoundException, EInOutError — подклассы. Всегда помещайте наиболее специфичное исключение первым, а Exception (базовый) последним. Никогда не оставляйте пустой except (молча подавляет ошибки).
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('сообщение'). CreateFmt — как Format + Create. Пользовательские исключения наследуются от Exception (или специфичного подкласса) и могут нести дополнительные данные (TransactionId). При оборачивании сохраняйте оригинал через SetInner или параметр конструктора. Пользовательские исключения позволяют вызывающим перехватывать специфичные типы ошибок: перехватывать ETransactionError отдельно от ERangeError. Всегда включайте значимое сообщение. Распространённые встроенные: ERangeError, EDivByZero, EConvertError, EFileNotFoundException, EAccessViolation, EListError.
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 проверяет условие и выбрасывает EAssertionFailed, если ложно — используйте для инвариантов (условия, которые всегда должны быть истинными). Утверждения отключаются через {$C-} (или удаляются в release-сборках) — не используйте их для проверки ввода (используйте исключения). OutputDebugString записывает в Журнал событий IDE (без файлового ввода-вывода). TStopwatch точно измеряет прошедшее время. Exception.StackTrace требует отладочной информации (.map-файл или JCLDebug/FastMM). {$IFDEF DEBUG} включает код только для отладки. Используйте утверждения для внутренних логических ошибок и исключения для пользовательских/внешних ошибок.
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) Циклы повторных попыток — оберните подверженную ошибкам операцию в try/except внутри цикла while, повторно выбрасывая после MaxRetries. (2) Резервные значения — перехватите специфичное исключение (EConvertError) и верните значение по умолчанию; подавляйте только те исключения, которые действительно ожидаете. (3) Защита ресурсов — всегда оборачивайте Create/Free в try/finally, чтобы объекты освобождались даже при исключении (это самая важная идиома Delphi). (4) Несколько ресурсов — вкладывайте блоки try/finally; получайте каждый ресурс в собственном защищённом блоке. (5) Проверка — выбрасывайте специфичные типы исключений (EArgumentException, ERangeError) рано с описательными сообщениями. Никогда не перехватывайте Exception и молча продолжайте — как минимум журналируйте. Предпочитайте try/finally для очистки и try/except для подлинного восстановления.
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, чтобы закрыть дескриптор файла.
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;Файловый ввод-вывод и потоки
Текстовые файлы (устаревшие и современные)
Два подхода: Устаревший (AssignFile/Reset/Rewrite/ReadLn/WriteLn/CloseFile) — классический Pascal, подходит для простого текстового ввода-вывода, но подвержен ошибкам (без исключений по умолчанию). Современный (TFile в System.IOUtils) чище: WriteAllText, ReadAllText, ReadAllLines, AppendAllText, Exists. Методы TFile выбрасывают исключения при ошибках (используйте try...except). Для больших файлов используйте StreamReader/StreamWriter (построчно, мало памяти). Всегда закрывайте файлы (CloseFile для устаревшего, или use try..finally). TFile предпочтителен для нового кода — он безопаснее и последовательнее.
uses
System.SysUtils, System.Classes, System.IOUtils;
// LEGACY: AssignFile / ReadLn / WriteLn (Pascal-style)
var
F: TextFile;
Line: string;
begin
// write
AssignFile(F, 'output.txt');
Rewrite(F); // create/overwrite
try
WriteLn(F, 'Hello, File!');
WriteLn(F, 'Second line');
finally
CloseFile(F);
end;
// append
AssignFile(F, 'output.txt');
Append(F);
try
WriteLn(F, 'Appended line');
finally
CloseFile(F);
end;
// read line by line
AssignFile(F, 'output.txt');
Reset(F); // open for reading
try
while not EOF(F) do
begin
ReadLn(F, Line);
WriteLn(Line);
end;
finally
CloseFile(F);
end;
end;
// MODERN: TFile (System.IOUtils)
var
Content: string;
Lines: TArray<string>;
begin
// write all text
TFile.WriteAllText('output.txt', 'Hello, World!');
// append
TFile.AppendAllText('log.txt', 'New entry' + sLineBreak);
// read all text
Content := TFile.ReadAllText('output.txt');
// read all lines
Lines := TFile.ReadAllLines('data.csv');
for Line in Lines do
WriteLn(Line);
// write all lines
TFile.WriteAllLines('nums.txt', ['one', 'two', 'three']);
// file exists?
if TFile.Exists('data.txt') then
WriteLn('Found');
end;TStringList для файлов и CSV
TStringList — самый простой способ обработки текстовых файлов и простых CSV. LoadFromFile/SaveToFile читают/записывают весь файл (по одной строке на элемент). CommaText разбивает/объединяет значения, разделённые запятыми; DelimitedText использует пользовательский разделитель. Values[] обрабатывает пары ключ=значение (как простой INI-файл). Sorted=True автоматически сортирует; Find выполняет бинарный поиск (быстрее, чем IndexOf для отсортированных списков). Duplicates управляет поведением при добавлении дубликатов (dupIgnore, dupAccept, dupError). Для сложных CSV (поля в кавычках с запятыми) используйте специализированный CSV-парсер. TStringList индексируется с 0.
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;Потоки и бинарный ввод-вывод
TFileStream — низкоуровневый байтовый ввод-вывод (чтение/запись буферов, Position для поиска). TBinaryWriter/Reader записывают/читают типизированные значения (Int32, Double, String, Boolean) — порядок чтения должен совпадать с порядком записи. TStreamReader/Writer обрабатывают текст с кодировкой (UTF-8, ASCII, Unicode) — используйте их для текстовых файлов с не-ASCII символами. Все потоки должны быть освобождены (try..finally). fmCreate создаёт/перезаписывает; fmOpenRead открывает только для чтения; fmOpenWrite открывает для записи. Для больших файлов читайте построчно через StreamReader (мало памяти) вместо LoadFromFile (загружает весь файл).
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).
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.
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;Компоненты VCL углублённо
Жизненный цикл формы и компонента
Формы VCL следуют строгому жизненному циклу: OnCreate (выделение ресурсов, инициализация) → OnShow (форма становится видимой) → OnActivate → OnResize → OnPaint → ... → OnCloseQuery (можно отменить закрытие) → OnClose → OnDestroy (освобождение ресурсов). Всегда парите OnCreate с OnDestroy для управления ресурсами. OnCloseQuery позволяет предотвратить закрытие (установите CanClose := False). Sender — компонент, вызвавший событие. Компоненты владеют своими дочерними элементами — освобождение формы автоматически освобождает все её компоненты.
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.
// 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 настраивают размеры. Опции вроде goEditing (редактируемые ячейки), goColSizing (изменение размера столбцов), goRowSelect включают поведение. OnDrawCell позволяет пользовательскую отрисовку с помощью Canvas. TDBGrid подключается напрямую к DataSet (TTable, TQuery) через TDataSource — он автоматически отображает и редактирует записи базы данных. Используйте TDBGrid для данных из БД, TStringGrid для данных в памяти.
// 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 содержит последующие столбцы. Оба поддерживают режим owner-data (виртуальный) для больших наборов данных через события OnGetNodeData/OnData.
// 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 возвращает True, если пользователь нажал OK. Filter устанавливает шаблоны типов файлов ('Описание|*.ext'). MessageDlg показывает модальные окна сообщений с типами (mtInformation, mtWarning, mtError, mtConfirmation) и наборами кнопок ([mbYes, mbNo, mbOK, mbCancel]). InputBox/InputQuery получают текстовый ввод пользователя. TPageControl управляет интерфейсом с вкладками со страницами TTabSheet. Все диалоги — невизуальные компоненты, размещаемые на форме.
// 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';Событийно-ориентированное программирование
События и обработчики событий
События в Delphi — указатели на методы (procedure of object). TNotifyEvent — стандартный тип события: procedure(Sender: TObject) of object. События — свойства — назначайте обработчики во время разработки (Инспектор объектов) или выполнения. Всегда проверяйте Assigned() перед вызовом обработчика события (он может быть nil, если не назначен). Sender — объект, вызвавший событие. Пользовательские события используют 'of object' для привязки к методам экземпляра. Параметры var (например, var Key: Char в OnKeyPress) позволяют обработчикам изменять значения — установите Key := #0 для подавления ввода.
// 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.Add для экземпляра Calc. Анонимные методы (reference to function) — современная альтернатива с семантикой замыканий. Указатели на методы — основа событийно-ориентированной архитектуры 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. Захваченные переменные переживают область объявления.
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.
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). OnIdle с Done := False создаёт плотный цикл; используйте осторожно. TTimer запускает OnTimer с интервалами (Interval в мс) — он основан на сообщениях, поэтому не срабатывает во время блокирующих операций. Application.ProcessMessages прокачивает очередь сообщений во время длительных операций (предотвращает 'Не отвечает'), но может вызывать ошибки повторного входа. TThread.Queue/Synchronize маршализуют обновления UI из фоновых потоков в основной поток.
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;Доступ к базе данных с 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.
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) обеспечивают визуальную привязку элементов управления к полям данных.
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) улучшает многопоточную производительность.
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 также поддерживает кэширование схемы для автономного доступа к метаданным.
// 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 и Local SQL
TFDMemTable — набор данных в памяти — идеально для кэширования, временных данных и модульного тестирования без базы данных. Определите поля через FieldDefs, затем CreateDataSet. AppendRecord добавляет строки. Поддерживает индексы, фильтры и всю навигацию по набору данных. Local SQL (TFDLocalSQL) позволяет выполнять SQL-запросы к любому TDataSet (включая TFDMemTable, TClientDataSet, даже Excel через ODBC) — обеспечивая соединения между таблицами в памяти и таблицами БД. Это мощно для ETL, отчётности и создания слоёв данных, работающих автономно. TFDMemTable также может загружать/сохранять в бинарные или JSON-файлы для персистентности.
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;Дженерики и анонимные методы
Дженерические классы и методы
Дженерики (появились в Delphi 2009) обеспечивают типобезопасные контейнеры и алгоритмы. TStack<T> работает с любым типом T — компилятор создаёт специализированные версии. Это устраняет приведения типов во время выполнения (без приведения TObject) и перехватывает ошибки типов во время компиляции. Дженерические параметры типов используют синтаксис <T>. Поддерживаются дженерические методы, классы, записи и интерфейсы. Ограничения (class, constructor, interface) ограничивают, какие типы можно использовать. RTL предоставляет TList<T>, TDictionary<TKey,TValue>, TQueue<T>, TStack<T>, TObjectList<T> в System.Generics.Collections.
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 (например, T.Create с ограничением constructor). Без ограничений можно только присваивать/сравнивать T (без вызова методов). Вывод типов иногда позволяет опустить явные параметры типов. Ограничения необходимы для создания типобезопасных фреймворков и ORM.
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/поиск на основе Predicate использует предикаты анонимных функций. TryGetValue возвращает True и выводит значение, если найдено (избегает исключения). AddOrSetValue обновляет или вставляет. TObjectList<T> с OwnsObjects := True автоматически освобождает содержащиеся объекты при освобождении списка — предотвращая утечки памяти.
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 (Parallel Programming Library), обработчиков событий и операций в стиле LINQ. Анонимные методы имеют подсчёт ссылок и управляются автоматически.
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 может быть создан.
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;RTTI и рефлексия
Основы Extended RTTI
Extended RTTI (информация о типах во время выполнения), появившаяся в Delphi 2010, обеспечивает полную рефлексию: проверку типов, свойств, методов и полей во время выполнения. TRTTIContext — точка входа. GetType возвращает TRttiType для класса. GetProperties перечисляет опубликованные свойства. GetValue/SetValue динамически читают/записывают значения свойств, используя TValue (вариантоподобный тип). Только 'published' члены имеют RTTI по умолчанию (используйте директиву {$RTTI EXPLICIT ...} для большего). RTTI обеспечивает сериализацию (JSON/XML), ORM, внедрение зависимостей и визуальные дизайнеры. Она имеет небольшие накладные расходы на производительность, но обеспечивает мощное метапрограммирование.
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 и динамической диспетчеризации.
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 (опубликованные свойства). TypeKind различает классы, записи, интерфейсы, перечисления и т. д. AsInstance.MetaclassType даёт ссылку на класс для создания экземпляров. Это позволяет фреймворкам автоматически обнаруживать и связывать компоненты. Фреймворки Spring4D и DORM используют это для автоматического отображения ORM. Перечисление RTTI медленное — кэшируйте результаты для повторного использования.
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 (значения свойств, аргументы методов) и обеспечивает динамическую типизацию в статически типизированном языке. Это похоже на 'object' в C# с информацией о типе или динамическую природу Python. TValue обрабатывает примитивы, строки, объекты, массивы и записи. Используйте его при создании сериализаторов, движков скриптов или дженерических слоёв данных. Он имеет накладные расходы по сравнению с прямой типизацией, но обеспечивает максимальную гибкость.
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 перебирает опубликованные свойства, читает значения через RTTI и строит TJSONObject. JSONToObject обращает процесс. Этот шаблон питает REST-клиенты, системы конфигурации и слои ORM. Модуль REST.Json предоставляет TJson.ObjectToJsonString и TJson.JsonToObject для этого из коробки. Для продакшена добавьте атрибуты ([JsonProperty('name')]) для управления именами полей и обработки вложенных объектов, массивов и пользовательских типов. Сериализация на основе RTTI медленнее, чем рукописные мапперы, но намного более поддерживаема.
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;Интерфейсы и COM
Основы интерфейсов и подсчёт ссылок
Интерфейсы определяют контракты (сигнатуры методов) без реализации. TInterfacedObject обеспечивает подсчёт ссылок — когда последняя ссылка на интерфейс удаляется, объект освобождается автоматически (не нужно вызывать Free). Это автоматическое управление памятью в Delphi для интерфейсных объектов. GUID (['{...}']) обеспечивают COM-взаимодействие и проверки InterfaceAs/Supports. Класс может реализовывать несколько интерфейсов (TShape реализует и IMovable, и IDrawable). Свойства интерфейсов разрешены (должны иметь методы чтения/записи). Всегда используйте типы интерфейсов (IMovable), а не типы классов (TShape), чтобы подсчёт ссылок работал. Смешивание объектных и интерфейсных ссылок может вызвать преждевременное освобождение.
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 от конкретных реализаций: замените TConsoleLogger на TFileLogger без изменения TUserService. Сам TUserService не имеет подсчёта ссылок (наследуется от TObject, не от TInterfacedObject), поэтому требует ручного Free. Для полноценного DI используйте контейнер (Spring4D, DSharp), который разрешает зависимости по типу интерфейса: Container.RegisterType<ILogger, TConsoleLogger>; Container.Build; Service := Container.Resolve<TUserService>. DI улучшает тестируемость (внедрение заглушек), поддерживаемость и модульность. Всегда зависите от абстракций (интерфейсов), а не от конкретных реализаций.
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 (Component Object Model) позволяет Delphi взаимодействовать с Windows-приложениями и библиотеками. CreateOleObject создаёт COM-объекты через позднее связывание (тип Variant — без проверки во время компиляции, но просто). Import Type Library генерирует модули с ранним связыванием и типизированными интерфейсами (IntelliSense, проверка типов, лучшая производительность). IUnknown — базовый COM-интерфейс с AddRef/Release/QueryInterface для подсчёта ссылок. stdcall — соглашение о вызовах COM. CoCreateInstance — низкоуровневый API. Распространённые применения COM: автоматизация Office (Excel, Word), ADO (база данных), интеграция оболочки, запросы WMI. Всегда вызывайте CoInitialize перед COM-операциями в потоках. COM-объекты используют апартаментную потоковую модель — осторожно маршализуйте между потоками.
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 exposes ICache, делегируя FCache (TMemoryCache). Это чище, чем наследование, и позволяет смешивать и сочетать поведение. Supports() проверяет, реализует ли объект интерфейс (использует QueryInterface внутри). Оператор As выполняет проверенное приведение интерфейса. Делегирование интерфейсов обеспечивает шаблон декоратор (обернуть кэш журналированием), шаблон стратегия (замена реализаций кэша) и чистое разделение задач. QueryInterface из COM — лежащий в основе механизм — каждый интерфейсный объект может быть запрошен для любого поддерживаемого интерфейса.
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] разрывает циклы — отслеживает ссылку, но не увеличивает счётчик ссылок и автоматически обнуляется при освобождении цели. [Unsafe] — сырой указатель (без отслеживания, без счётчика ссылок) — самый быстрый, но опасный (висячие указатели). Используйте [Weak] для родительских/обратных ссылок, шаблонов наблюдателя и подписок на события. Сильная ссылка по умолчанию увеличивает счётчик ссылок и удерживает объект в живых. ARC в Delphi (устаревший в пользу [Weak]) ранее обрабатывал это автоматически на мобильных. На десктопе интерфейсы используют ручной подсчёт ссылок — [Weak] необходим для designs без циклов. Всегда правильно парите сильные и слабые ссылки.
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;Многопоточность и PPL
Основы TThread
TThread — основа многопоточности Delphi. Переопределите Execute с фоновой работой. Периодически проверяйте Terminated для корректной отмены. TThread.Synchronize выполняет код в главном потоке (блокирующий — ждёт завершения); TThread.Queue асинхронный (отправляет и возвращается немедленно). НИКОГДА не обращайтесь к элементам управления UI из фоновых потоков — всегда используйте Synchronize или Queue. FreeOnTerminate := True автоматически освобождает поток при завершении Execute. CreateAnonymousThread создаёт одноразовый поток из анонимного метода — удобно для простых задач. Для продакшен-кода предпочитайте PPL (TTask) вместо сырого TThread для лучшей композиции и обработки ошибок.
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)
Библиотека параллельного программирования (PPL) в System.Threading обеспечивает высокоуровневую конкурентность: TTask (асинхронность fire-and-forget), TTask.Future<T> (асинхронность с возвращаемым значением) и параллельные циклы. Задачи используют пул потоков автоматически — не нужно управлять потоками. WaitForAll/WaitForAny компонуют несколько задач. Future.Value блокирует до готовности результата (как обещание). PPL — современная альтернатива сыровому TThread — чище, композируемо и интегрируется с шаблонами async/await. Задачи захватывают исключения и повторно выбрасывают их при обращении к .Value, обеспечивая корректное распространение ошибок. Используйте TEvent/TCountdownEvent для мелкозернистой синхронизации между задачами.
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. Избегайте параллелизации циклов с малым числом итераций или тяжёлым вводом-выводом (пул потоков истощается). Stride управляет шагом итерации. Вложенные параллельные циклы редко помогают — внешний цикл уже насыщает ядра.
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 для атомарных счётчиков.
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.
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;Сетевое программирование с Indy
TCP-клиент и сервер (Indy)
Indy (Internet Direct) — встроенная сетевая библиотека Delphi. TIdTCPClient подключается к серверам — WriteLn/ReadLn для строковых протоколов, Write/Read для бинарных. ConnectTimeout предотвращает зависание. TIdTCPServer слушает соединения — OnExecute выполняется в отдельном потоке на клиента (AContext представляет каждое соединение). Indy использует блокирующие сокеты (простая модель — без обратных вызовов), поэтому обработчики сервера выполняются в рабочих потоках. Всегда корректно обрабатывайте отключения. Для высокопроизводительных серверов рассмотрите ICS (перекрываемый ввод-вывод) или Synapse. Компоненты Indy невизуальные — перетащите на форму или создайте в коде. Установите Active := True для начала прослушивания. DefaultPort устанавливает порт прослушивания.
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 — HTTP-клиент Indy — поддерживает GET, POST, PUT, DELETE, заголовки, cookies и 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 (легче). Всегда освобождайте HTTP и обработчик SSL в блоках finally. Установите Http.HandleRedirects := True для автоматического следования 301/302 перенаправлениям.
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 email (TIdSMTP)
TIdSMTP отправляет email через SMTP-серверы. TIdMessage представляет письмо (From, Recipients, Subject, Body). Для Gmail/Office365 используйте TLS (порт 587, utUseExplicitTLS) или SSL (порт 465, utUseImplicitTLS). Gmail требует 'Пароль приложения' (не обычный пароль) с включённым 2FA. TIdAttachmentFile добавляет вложения файлов. Для HTML-писем установите ContentType := 'text/html'. Для multipart (HTML + простой текст + вложения) используйте TIdMessageBuilderHTML. Распространённые порты: 25 (без шифрования/relay), 465 (SSL), 587 (STARTTLS). Всегда оборачивайте Connect/Send в try/finally для гарантии Disconnect. Для приёма почты используйте TIdPOP3 или TIdIMAP4.
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.
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 directlyFTP и 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 использует плейсхолдеры {param}, заполняемые через AddUrlSegment. Execute отправляет запрос; RESTResponse.Content содержит тело; JSONValue автоматически анализирует JSON. REST-компоненты поддерживают OAuth2, базовую аутентификацию и пользовательские аутентификаторы. Для высокопроизводительного REST рассмотрите TNetHTTPClient (легче) или TIdHTTP из Indy для максимального контроля.
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;DLL и BPL-пакеты
Создание и использование DLL
DLL (Dynamic Link Libraries) разделяют код между приложениями. Используйте ключевое слово '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).
// --- 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 экспортирует фабричную функцию (CreatePlugin), возвращающую IPlugin. Хост-приложение определяет тот же интерфейс (тот же GUID!) и вызывает фабрику. Подсчёт ссылок интерфейса обрабатывает очистку автоматически. Используйте PChar для строк (не Delphi string), чтобы избежать конфликтов менеджеров памяти. Это шаблон плагинной архитектуры — загружайте DLL динамически, создавайте плагины через фабрику, общайтесь через интерфейсы. Для полноценных плагинных систем рассмотрите плагинный фреймворк в Delphi или используйте пакеты (BPL), которые разделяют RTL и позволяют прямое разделение классов.
// --- 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 Package Library)
BPL (Borland Package Libraries) — специфичные для 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-only BPL мощнее.
// --- 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) Шаблон caller-allocates — вызывающая сторона предоставляет буфер, DLL заполняет его (самый безопасный, языконезависимый). (3) SimpleShareMem/FastMM — современный разделяемый менеджер памяти (FastMM по умолчанию с Delphi 2006). (4) Освобождение через обратный вызов — DLL предоставляет функцию освобождения. Для возвратов PChar используйте StrNew/StrDispose (Windows API, разделяемые). Для продакшена Delphi-в-Delphi используйте BPL (разделяемый RTL) или SimpleShareMem. Для кросс-языкового всегда используйте шаблон caller-allocates. Никогда не передавайте типы Delphi string/object/interface через границы DLL без разделяемого менеджера памяти.
// 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 — универсальный тип бинарного ресурса.
// --- 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Отладка и настройка производительности
Отладчик и точки останова
Отладчик IDE Delphi мощный: устанавливайте точки останова кликом по полю. Условные точки останова прерываются только когда выражение истинно (например, i > 100). Точки журнала/трассировки записывают сообщения без остановки — отлично для мониторинга циклов. asm int 3 end создаёт жёсткую точку останова в коде (ловушка CPU). OutputDebugString записывает в окно Журнала событий (и инструмент DebugView). Assert проверяет условия в отладочных сборках (отключается через {$C-} или утверждениями выкл в release). DebugHook не равен нулю при запуске в IDE. Окно Call Stack отслеживает цепочку вызовов; окно Threads проверяет все потоки; Local Variables показывает текущую область. Включите 'Use Debug DCUs' для входа в исходный код RTL/VCL.
// 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 — они захватывают стеки вызовов, дампы регистров и даже отправляют отчёты о сбоях по email. Всегда журналируйте исключения для посмертной отладки. Никогда не подавляйте исключения молча в продакшене.
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 — они определяют горячие точки без изменения кода. Оптимизируйте 20% кода, занимающие 80% времени.
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. Правило №1: каждый Create должен иметь соответствующий Free. Религиозно используйте try/finally. Для автоматического управления используйте интерфейсы (TInterfacedObject + подсчёт ссылок) — не нужно Free. TObjectList<T> с OwnsObjects := True освобождает содержащиеся объекты автоматически. ReportMemoryLeaksOnShutdown := True показывает диалог со списком утекших объектов при выходе (только отладка). FastMM (менеджер памяти по умолчанию) в FullDebugMode журналирует утечки со стеками вызовов выделения в файл — необходимо для отслеживания утечек. Распространённые шаблоны утечек: отсутствие try/finally, не удалённые обработчики событий, циклические ссылки (исправьте через [Weak]), не освобождённые потоки, не освобождённые глобальные объекты в finalization. Секция finalization модуля выполняется при завершении — используйте её для глобальной очистки.
// 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 (непрерывная интеграция) для раннего выявления регрессий. Интеграционные тесты проверяют совместную работу компонентов; модульные тесты проверяют отдельные модули изолированно.
// 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;Дженерики и коллекции
Объявление дженерического класса
Дженерики позволяют писать типобезопасные контейнеры без приведения типов. Объявляйте с <T> после имени типа. Компилятор генерирует специализированную версию для каждого используемого типа. Используйте TArray<T> вместо array of для динамических массивов в дженерических типах.
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 (не исключение) при отсутствии ключа. Всегда освобождайте словари — по умолчанию они не владеют объектами.
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 принимает открытый массив или другой список.
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.
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 принадлежащие объекты освобождаются автоматически — предотвращает утечки памяти в коллекциях объектов.
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;Анонимные методы и замыкания
Базовый анонимный метод
Анонимные методы — встроенные ссылки на функции. TFunc<...> для функций, TProc<...> для процедур. Они захватывают переменные из окружающей области видимости (замыкания). Присваиваемы переменным, передаваемы как параметры.
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 — замыкания независимы. Так работают фабрики и частичное применение.
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> для динамических массивов.
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.
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 оборачивает замыкание в поток — фоновая работа fire-and-forget. Используйте TThread.Queue (или Synchronize) для маршализации обновлений UI обратно в главный поток. Никогда не касайтесь элементов UI напрямую из рабочего потока.
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;Атрибуты и RTTI
Объявление пользовательского атрибута
Атрибуты — классы, наследующие TCustomAttribute. Применяйте с [AttrName(...)] к типам, полям, мет одам, свойствам. Компилятор встраивает их в RTTI. Параметры конструктора становятся аргументами атрибута.
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.
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 возвращает все публичные/опубликованные поля. SetValue/GetValue обеспечивают динамический доступ к полям по имени — полезно для сериализаторов и ORM. GetMethods возвращает все методы, включая унаследованные. RTTI медленнее прямых вызовов.
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 возвращает опубликованные свойства. IsReadable/IsWritable проверяют аксессоры. GetValue/SetValue работают и со свойствами. TypeKind (tkInteger, tkString, tkClass и т. д.) позволяет обрабатывать каждый тип соответствующе. Так работают большинство сериализаторов 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 и т. д.
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;Интерфейсы углублённо
Объявление и реализация интерфейса
Интерфейсы определяют контракты без реализации. GUID (необязательны, но рекомендуются) обеспечивают приведения 'as' и Supports(). TInterfacedObject обеспечивает подсчёт ссылок. Все методы интерфейса должны быть реализованы (нет выхода через 'abstract'). Свойствам в интерфейсах нужны методы-аксессоры.
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;Подсчёт ссылок и память
Ссылки на интерфейсы имеют подсчёт ссылок. Когда последняя ссылка на интерфейс выходит из области видимости, объект освобождается. НИКОГДА не смешивайте объектные и интерфейсные ссылки на один экземпляр — подсчёт ссылок интерфейса освободит его, пока объектный указатель всё ещё указывает на него. Выберите одну модель владения.