Skip to content

Delphi チートシート

ラピッドアプリケーション開発向けのObject Pascal方言。

01

プログラム構造と基本

プログラム構造とユニット

Delphiプログラムは'program'で始まり、'end.'(ピリオド付き)で終わる。{$APPTYPE CONSOLE}はコンパイラ指令でコンソールアプリとしてマークする。'uses'はユニット(モジュール)をインポート — System.SysUtilsにはFormat、IntToStrなどがある。ユニットにはinterface部(公開宣言)とimplementation部(コード)がある。WriteLnは改行付きでテキストを出力、Writeは改行なし。ReadLnは入力を読み取る(または一時停止)。メインのbegin..endブロックがプログラムのエントリポイント。

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

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

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

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

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

変数、型と定数

Delphiは強く型付けされる。一般的な型:Integer(32ビット)、Int64(64ビット)、Double(64ビット浮動小数点)、Extended(x86では80ビット浮動小数点)、Single(32ビット浮動小数点)、string(Unicode、参照カウント付き)、Char(WideChar、2バイト)、Boolean、Byte(0-255)。TDateTimeは実際にはDouble(1899-12-30からの日数)。定数は'const'を使用 — 型付き定数は型を持ち、型なしは柔軟。部分範囲型(0..150)は値を制限。列挙型(TDay)は名前付き定数を定義。Format()はsprintfのようなもの:%s(文字列)、%d(整数)、%f(浮動小数点)。

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

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

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

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

演算子と式

Delphiは代入に:=を使用し、等価に=を使用する(C系言語の逆)。divは整数除算、/は実数除算(常にExtended/Doubleを返す)。modは剰余。and/or/not/xorはBoolean(論理)と整数(ビット単位)の両方で動作 — 文脈で決まる。shl/shrはビットシフト。Inc/Decは効率的なインプレメント増減分(A := A + 1と書かない)。文字列連結には+を使用。Power()はSystem.Mathにある。:=と=の区別は初心者のエラーの最大の原因。

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

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

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

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

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

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

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

入出力とフォーマット

Format()はDelphiのsprintf — %s(文字列)、%d(整数)、%f(浮動小数点)、%x(16進数)、%m(通貨)を使用し、幅/精度修飾子付き。WriteLn(value:width:decimals)は浮動小数点を直接フォーマット。ReadLnは入力を変数に読み取る。StrToInt/StrToFloatは文字列を数値に変換(失敗時にEConvertErrorをスロー)、TryStrToIntはBooleanを返し安全。IntToStr/FloatToStrは数値を文字列に変換。FormatDateTimeは日付をフォーマット(yyyy、mm、dd、hh、nn、ss)。FloatToStrFは精密な制御を提供(ffFixed、ffCurrency、ffExponent)。

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

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

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

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

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

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

ユニット、スコープと可視性

ユニットはDelphiのモジュール。interface部は公開内容(ユーザーに見える)を宣言、implementation部はコードを含みプライベートな型/変数を持てる。initialization/finalization部はユニットのロード/アンロード時に実行(ユニットのコンストラクタ/デストラクタのようなもの)。interfaceで宣言された変数はグローバル、implementationではユニットプライベート。interface部の型は公開、implementation部ではプライベート。この2部構成によりユニットレベルでカプセル化を強制。'uses'節は他のユニットをインポート — UnitName.Identifierで名前の衝突を解決。

delphi
// Unit declaration
unit MathHelper;

interface

uses
  System.SysUtils;

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

// public constants
const
  Pi = 3.14159265358979;

// public variables
var
  Counter: Integer;

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

implementation

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

// private variables
var
  InternalCount: Integer;

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

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

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

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

end.
02

制御フロー

If...Then...Else

If...Then...ElseはDelphiの条件分岐。重要:'else'の前にセミコロンなし — セミコロンは文を終了し、elseはifの一部。複数文の分岐にはbegin..endで囲む(それでもelseの前にセミコロンなし)。and/or/notは論理演算子(整数ではビット単位でも)。括弧で条件をグループ化:(A > 0) and (B > 0)。else前のセミコロン忘れは初心者に最も多いDelphi構文エラー。

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

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

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

  WriteLn('Grade: ', Grade);

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

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

Case(Switch)文

CaseはDelphiのswitch — 順序型(Integer、Char、列挙型、部分範囲)で動作。各分岐は単一値、カンマ区切りリスト('D', 'F')、または範囲(1..5)。else節がデフォルト。Caseはフォールスルーしない(Cと異なる)。複数文の分岐にはbegin..endを使用。離散値にはif-elseの連鎖よりCaseがクリーン。文字列には直接caseできない(if-elseまたはルックアップを使用)。

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

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

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

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

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

Forループ(To、Downto、In)

For...toは昇順、For...downtoは降順で反復。ループ変数はループ内で変更不可。For...in(モダンDelphi)は配列、文字列(文字ごと)、セット、任意の列挙可能なものを反復。Breakはループを脱出、Continueは次の反復へスキップ。組み込みのステップはない — 条件またはwhileループを使用。ループ変数はループ後に未定義(値に依存しない)。コレクションにはFor...inが推奨(クリーン、インデックスエラーなし)。

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

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

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

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

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

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

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

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

WhileとRepeat...Until

Whileは本体の前にテスト(実行されない可能性あり)、repeat...untilは後にテスト(常に少なくとも1回実行)。重要:whileは条件がTRUEの間継続、repeatは条件がTRUEになったら停止(逆のロジック!)。repeat...untilはbegin..end不要(本質的にブロック)。'ゼロ回以上'にはwhile、'1回以上'にはrepeatを使用。Breakは脱出、Continueはテストへスキップ。while True with Breakは複雑な終了条件を持つループの一般的なイディオム。

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

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

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

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

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

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

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

With...DoとGoto

With...Doはレコード/オブジェクトのメンバーに変数を繰り返さずにアクセス — 初期化と冗長性削減に便利。ネストしたWithは避ける(メンバーがどのオブジェクトに属するか曖昧)。Gotoはラベルにジャンプ — モダンDelphiでは稀(Break/Continue/Exitを推奨)、'label'でラベルを宣言。Exitはプロシージャを即座に離脱、Exit(value)は関数から値を返す(モダン構文)。Withは過剰に使用するとコードが読みにくくなる — 単純なケースに控えめに使用。

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

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

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

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

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

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

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

文字列とテキスト処理

文字列型と操作

Delphiのデフォルト文字列はUnicodeString(UTF-16、参照カウント付き、コピーオンライト)。文字列は1インデックス(S[1]が最初の文字)— Cプログラマのバグの一般的な原因。Length()は文字数を返す。Pos()は部分文字列を検索(見つからない場合は0を返す、-1ではない)。Copy()は部分文字列を抽出(Start、Count)。StringReplaceは置換(rfReplaceAllですべての出現)。Trim/TrimLeft/TrimRightは空白を削除。Split/Joinはモダンメソッド(TArray<string>)。大文字小文字を区別しない比較にはSameTextを使用。

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

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

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

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

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

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

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

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

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

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

文字列フォーマットと変換

Format()はDelphiのsprintf:%d(整数)、%f(浮動小数点)、%s(文字列)、%x(16進数)、%m(通貨)、幅/精度修飾子付き。FloatToStrFは精密な制御を提供(ffFixed、ffCurrency、ffNumber、ffExponent)。FormatDateTimeは日付をフォーマット:yyyy(4桁年)、mm(月)、dd(日)、hh(時)、nn(分)、ss(秒)、dddd(完全な曜日名)、mmmm(完全な月名)。StrToInt/StrToFloatは無効な入力でEConvertErrorをスロー、TryStrToIntはBooleanを返す(安全)。ユーザー入力には常にTry...を使用。

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

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

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

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

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

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

StringBuilderとTStringList

StringBuilder(可変)は大きな文字列を構築するループに効率的 — Appendは新しい文字列を作らずインプレースで変更。TStringListはDelphiのスイスアーミーナイフ:ソート、検索、key=valueペアの保持(Values[])、ファイルの読み込み/保存(1行1アイテム)、区切りテキストの分割(CommaText、DelimitedText)ができる文字列リスト。TStringListは0インデックス(SL[0])で文字列(S[1])と異なる。常にtry..finallyでFree。Delphiでテキストファイルとシンプルな設定を処理する最も一般的な方法。

delphi
uses
  System.SysUtils, System.Classes;

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

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

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

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

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

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

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

Char操作とエンコーディング

Charは2バイトUnicode文字。Ord()はコードポイントを取得、Char()は逆変換。IsDigit/IsLetter/IsWhiteSpace/IsUpper/IsLowerが文字を分類。ToUpper/ToLowerがケースを変換。TEncoding.UTF8.GetBytesが文字列をバイト配列に変換(ファイルI/Oとネットワーキングに不可欠)— UTF-8は1文字あたり1-4バイト使用。TEncoding.UnicodeはUTF-16 LE(常に2バイト/文字)。Base64(TNetEncoding.Base64)はバイナリデータをテキストとして転送用にエンコード。StringとCharは内部的にUTF-16、ファイルストレージとネットワークプロトコルにはUTF-8に変換。

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

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

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

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

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

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

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

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

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

正規表現

System.RegularExpressionsがパターンマッチング用のTRegexを提供。IsMatchがテスト、Matchが最初を検索、Matchesがすべてを検索。グループは括弧で部分をキャプチャ — Groups[1]、Groups[2]でアクセス(1インデックス)。Replaceがマッチを置換($1、$2がグループを参照)。Splitがパターンで分割。一般的な正規表現:\d(数字)、\w(単語文字)、\s(空白)、+(1回以上)、*(0回以上)、{n}(ちょうどn回)、^/$(開始/終了)。roCompiledは繰り返し使用のためにコンパイルで高速化。ユーザー入力(メール、電話)は常に正規表現で検証。

delphi
uses
  System.RegularExpressions;

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

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

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

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

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

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

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

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

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

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

配列、レコードとコレクション

静的配列と動的配列

静的配列はコンパイル時に固定サイズでカスタムインデックス範囲を持つ(array[0..4]またはarray[1..7])。動的配列(array of T)はSetLengthでサイズ変更可能 — 0インデックスで参照カウント付き。High()は最後のインデックスを返す、Length()はカウントを返す。既存の動的配列にSetLengthするとサイズ変更(拡大時に既存の値を保持)。nilで解放。動的配列リテラルは[1, 2, 3]を使用。多次元動的配列は'配列の配列'(ジャグ)— 各行が異なる長長さを持てる。

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

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

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

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

  Dyn := nil;                    // free memory

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

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

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

レコード(構造体)

レコードは値型(代入時にコピー、スタック割り当て)— Cのstructのようなもの。モダンDelphiのレコードはメソッド、プロパティ、可視性(private/public)を持てる。レコードは解放不要(ヒープ割り当てなし)。バリアントレコード(case...of)はフィールドがメモリを共有するユニオンを作成 — 型タグに便利。小さく軽量なデータ(ポイント、座標、設定)にはレコードを使用。継承やポリモーフィズムが必要な大きなオブジェクトにはクラスを使用。レコードは高速(ヒープ割り当てなし)だが継承不可。

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

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

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

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

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

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

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

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

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

セットと列挙型

セットはDelphiのユニークな機能 — 列挙型または部分範囲からの値のコレクション(最大256要素)。演算子:+(和集合)、-(差集合)、*(積集合)、=(等価)、<=(部分集合)、in(所属)。Include/Excludeは効率的な単一要素の追加/削除。セットはビットマップとして保存(非常に高速)。一般的な使用:TFontStyles(fsBold、fsItalic)、検証用のCharのセット(['0'..'9'])、曜日。列挙型は順序型 — Low()からHigh()で反復、GetEnumNameで文字列に変換。セットはフラグの組み合わせをエレガントで型安全にする。

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

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

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

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

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

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

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

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

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

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

TList、TDictionaryとジェネリクス

System.Generics.Collectionsが型安全なコレクションを提供:TList<T>(動的配列)、TDictionary<K,V>(ハッシュマップ)、TQueue<T>(FIFO)、TStack<T>(LIFO)、THashSet<T>(一意要素)。すべてジェネリック(コンパイル時型チェック、キャストなし)。TListはAdd/Remove/Delete/Sort/Contains/IndexOfを持つ。TDictionaryはAdd/Remove/TryGetValue/Keys/Valuesを持つ。TObjectList<T>はオブジェクトを所有(自動的に解放)— リストがオブジェクトのライフタイムを管理すべき場合に使用。常にtry..finallyでFree(これらはレコードではなくオブジェクト)。

delphi
uses
  System.Generics.Collections;

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

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

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

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

配列アルゴリズムとソート

TArrayは配列操作のユーティリティクラス:Sort(オプションのカスタムIComparer付き)、BinarySearch(ソート済み配列の高速検索)、Reverse、Copy。TComparer<T>.Constructがインラインで比較関数を作成(匿名メソッド)。フィールドでソートするにはカスタム比較子が必要。BinarySearchはBooleanと見つかったインデックスを返す — 配列は事前にソート済みでなければならない。複雑な検索にはBreak付きの線形ループがシンプルで明確。TArray.Sortはクイックソート(平均O(n log n))。

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

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

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

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

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

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

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

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

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

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

プロシージャ、関数とパラメータ

プロシージャと関数

プロシージャ(戻り値なし)と関数(値を返す)はDelphiのサブルーチン。Result変数が戻り値 — それに代入(関数は終了時に返す)。Exit()は値付きで即座に返す(モダン構文)。前方宣言により本体が定義される前に呼び出し可能(相互再帰に便利)。関数はレコード、配列、オブジェクトを含む任意の型を返せる。値なしのExitはプロシージャを離脱するだけ。Result変数は暗黙的に宣言され、戻り値の型に一致。

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

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

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

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

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

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

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

パラメータ:Const、Var、Out、Default

const:読み取り専用パラメータ(文字列/配列のコピーも回避 — 効率的)。var:参照渡し(呼び出し元の変数を変更 — C#のrefのようなもの)。out:出力専用(呼び出し元は初期化しない、関数が設定)。デフォルトパラメータは最後でなければならない。オープン配列パラメータ(array of T)は任意の配列またはリテラル[1,2,3]を受け入れる — 効率のためconstを使用。文字列と配列にはconstが推奨(コピーなし)、呼び出し元の値を変更する必要がある場合のみvarを使用。オープン配列は元の配列の範囲に関わらず0インデックス。

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

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

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

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

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

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

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

オーバーロードとデフォルトパラメータ

オーバーロードにより異なるパラメータリストで同じ名前を共有 — コンパイラが最適なマッチを選択。'overload'指令が必須。オーバーロードは異なる名前を考える(AddInt、AddDouble)よりクリーン。デフォルトパラメータは代替 — 呼び出し側が省略可能。型によってロジックが異なる場合はオーバーロードを推奨、オプション値にはデフォルトを使用。曖昧さ(同等にマッチする2つのオーバーロード)はコンパイルエラー。オーバーロードはパラメータ数または型で異なる必要がある(戻り値の型だけでは不十分)。

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

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

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

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

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

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

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

匿名メソッドとクロージャ

匿名メソッド(クロージャ)は'reference to'型に代入されるインライン関数/プロシージャ。外側のスコープから変数をキャプチャ(クロージャ)。'reference to function'/'reference to procedure'がデリゲート型。匿名メソッドは関数型プログラミングを可能に:高階関数(Applyが関数を取る)、クロージャ(MakeMultiplierがFactorを覚える関数を返す)、カスタム比較子(TComparer<T>.Construct)。ジェネリクスのソート、イベントハンドラ、コールバックに不可欠。キャプチャされた変数はヒープ割り当て(外側の関数より長生き)。

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

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

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

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

  WriteLn(Double(21));    // 42

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

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

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

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

再帰とヘルパールーチン

再帰は関数が自分自身を呼び出す — 終了のためのベースケースが必要。階乗とフィボナッチが古典的な例。末尾再帰(再帰呼び出しが最後の操作)はコンパイラが最適化可能。ネストされたプロシージャ/関数は別のルーチン内で宣言され、その変数にアクセス可能(レキシカルスコープ)— 外部に見える必要のないヘルパーに便利。深い再帰でスタックオーバーフローに注意(大きな入力には反復を使用)。メモ化(結果のキャッシュ)はフィボナッチのような再帰アルゴリズムを高速化可能。

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

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

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

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

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

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

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

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

クラスとOOP

クラス定義、コンストラクタとデストラクタ

クラスは参照型(ヒープ割り当て、ポインタ経由でアクセス)。Createがコンストラクタ、Destroyがデストラクタ(常にoverride、Freeから呼ばれる)。'inherited'が基底クラスのメソッドを呼ぶ。フィールドは慣習でF接頭辞を使用。プロパティ(property X: Type read GetX write SetX)が制御されたアクセスを提供 — 呼び出し側はP.Ageを使うがセッターが検証。可視性:private(古いDelphiではユニットのみ、strict privateが真にプライベート)、protected(サブクラス)、public(全員)、published(RTTI、フォーム/インスペクタ用)。オブジェクト作成は常にtry..finallyで囲みFreeが呼ばれることを保証。

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

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

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

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

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

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

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

プロパティとインデックス付きプロパティ

プロパティはゲッター/セッターでフィールドアクセスをカプセル化。読み取り専用プロパティは'read'指定子のみ。'default'指令がインデックス付きプロパティをデフォルトに — L.Items[i]の代わりにL[i]が動作。プロパティは直接フィールドアクセス(read FCount)またはメソッドアクセス(read GetItem write SetItem)で検証/計算可能。インデックス付きプロパティが配列風構文を可能に。公開プロパティ(published部)はRTTIとフォームデザイナに見える。プロパティはDelphiでデータを安全に公開する方法 — 公開フィールドより常に推奨。

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

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

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

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

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

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

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

継承とポリモーフィズム

継承:TDog = class(TAnimal)はTDogがTAnimalを継承することを意味。'virtual'がポリモーフィズム用のメソッドをマーク、'override'がサブクラスで置換。実行時、実際のオブジェクトのメソッドが実行(仮想ディスパッチ)— TAnimal参照がTDogを保持する場合のSpeak呼び出しはTDog.Speakを呼ぶ。静的メソッド(Move)はオブジェクトの型ではなく変数の型で決まる。'inherited'が基底メソッドを呼ぶ。コンストラクタは仮想可能(ファクトリパターン)。ポリモーフィズムにはvirtual/overrideを使用、振る舞いが固定の場合は静的メソッド。作成したオブジェクトは常に解放。

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

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

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

constructor TAnimal.Create;
begin
  inherited;
end;

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

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

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

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

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

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

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

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

抽象メソッドとクラスメソッド

抽象クラス(class abstract)はインスタンス化不可 — サブクラスの契約を定義。抽象メソッド(virtual; abstract)は実装を持たない — サブクラスがオーバーライド必須。これによりすべての図形がArea/Perimeterを提供することを強制。クラスメソッド(class function/procedure)はインスタンス不要 — TShape.ShapeCountで呼び出し。クラス変数(class var)は全インスタンスで共有。テンプレートメソッドパターン:TShape.Describeが抽象Area/Perimeterを呼び、サブクラスが埋める。抽象メソッドが'何'を定義、サブクラスが'どう'を定義。

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

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

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

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

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

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

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

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

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

インターフェースと多重継承

インターフェースは純粋な契約(フィールドなし、実装なし)— Delphiの型の多重継承を実現する方法。クラスは多数のインターフェースを実装可能(TButtonはIComparable、IDrawable、IDisposableを実装)。インターフェースはQueryInterface/asキャスト用にGUIDを持てる。TInterfacedObjectが参照カウントを提供 — 最後のインターフェース参照がスコープを抜けるとオブジェクトは自動的に解放(Freeを呼ぶ必要なし)。疎結合にインターフェースを使用:TButtonではなくIDrawableに依存するコード。'as'演算子がインターフェースにキャスト(サポートされない場合はスロー)。インターフェースはDelphiのCOMサポートとモダンなプラグインアーキテクチャの骨格。

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

  IDrawable = interface
    procedure Draw;
  end;

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

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

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

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

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

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

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

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

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

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

例外とエラー処理

Try...Except...Finally

try...exceptは例外をキャッチ(C#のtry/catchのようなもの)。各'on E: ExceptionType do'が特定の例外を処理。try...finallyは例外に関わらずクリーンアップを保証(例外処理なし — Free呼び出しに使用)。パターンはtry...try...except...finally(内側のexceptで処理、外側のfinallyでクリーンアップ)。'raise'(裸)が現在の例外を再スロー。Exceptionが基底クラス、EFileNotFoundException、EInOutErrorがサブクラス。常に最も具体的な例外を最初に、Exception(基底)を最後に。空のexceptは絶対に残さない(エラーを暗黙に飲み込む)。

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

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

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

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

例外の発生とカスタム例外

Raiseが例外を作成:raise ExceptionType.Create('message')。CreateFmtはFormat + Createのようなもの。カスタム例外はException(または特定のサブクラス)を継承し、追加データ(TransactionId)を持てる。ラップする際、SetInnerまたはコンストラクタパラメータで元の例外を保持。カスタム例外により呼び出し側が特定のエラー型をキャッチ可能:ERangeErrorとは別にETransactionErrorをキャッチ。常に意味のあるメッセージを含める。一般的な組み込み:ERangeError、EDivByZero、EConvertError、EFileNotFoundException、EAccessViolation、EListError。

delphi
uses
  System.SysUtils;

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

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

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

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

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

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

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

アサーションとデバッグ

Assertは条件をチェックし、falseの場合EAssertionFailedをスロー — 不変条件(常に真でなければならない条件)に使用。アサーションは{$C-}で無効化(またはリリースビルドで削除)— 入力検証には使用しない(例外を使用)。OutputDebugStringはIDEのイベントログに出力(ファイルI/Oなし)。TStopwatchが経過時間を正確に測定。Exception.StackTraceにはデバッグ情報(.mapファイルまたはJCLDebug/FastMM)が必要。{$IFDEF DEBUG}がデバッグ専用コードを有効化。内部ロジックエラーにアサーション、ユーザー/外部エラーに例外を使用。

delphi
uses
  System.SysUtils, System.Diagnostics;

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

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

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

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

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

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

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

例外処理パターン

一般的な例外パターン:(1) リトライループ — 失敗する可能性のある操作をwhileループ内のtry/exceptで囲み、MaxRetries後に再スロー。(2) フォールバック値 — 特定の例外(EConvertError)をキャッチしてデフォルトを返す、本当に期待する例外のみを飲み込む。(3) リソース保護 — Create/Freeを常にtry/finallyで囲み、例外時もオブジェクトが解放されるように(これがDelphiの最も重要なイディオム)。(4) 複数リソース — try/finallyブロックをネスト、各リソースを独自の保護ブロック内で取得。(5) 検証 — 説明的なメッセージで早期に特定の例外型(EArgumentException、ERangeError)をスロー。Exceptionをキャッチして暗黙に継続しない — 最低でもログに記録。クリーンアップにはtry/finally、真の回復にはtry/exceptを推奨。

delphi
uses
  System.SysUtils, System.Classes;

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

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

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

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

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

ロギングとエラーレポート

本番ロガーに必要:(1) スレッドセーフティ — TCriticalSectionが書き込みを直列化(複数スレッドが同時にログを取る可能性)。(2) 重大度レベル — TLogLevel列挙型でフィルタ可能(例:本番でllDebugを抑制)。(3) フォーマット出力 — DateTime + レベル + メッセージを1行に、後で解析可能。(4) 各書き込み後にフラッシュ — クラッシュでもログが残る(フラッシュされていないバッファ書き込みはAVで失われる)。(5) 例外ロギング — LogExceptionがClassName + Message + コンテキストをキャプチャ。ログして再スローパターンはエラーを記録しつつ上位レイヤーが処理可能。高性能ロギングにはロックフリーキューまたは外部ライブラリ(Log4Delphiなど)を検討。ファイルハンドルを閉じるためfinallyでロガーを常にFree。

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

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

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

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

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

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

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

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

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

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

ファイルI/Oとストリーム

テキストファイル(レガシーとモダン)

2つのアプローチ:レガシー(AssignFile/Reset/Rewrite/ReadLn/WriteLn/CloseFile)はクラシックPascal — シンプルなテキストI/Oには良いがエラーを起こしやすい(デフォルトで例外なし)。モダン(System.IOUtilsのTFile)はクリーン:WriteAllText、ReadAllText、ReadAllLines、AppendAllText、Exists。TFileメソッドはエラー時に例外をスロー(try...exceptを使用)。大きなファイルにはStreamReader/StreamWriterを使用(1行ずつ、低メモリ)。常にファイルを閉じる(レガシーはCloseFile、またはtry..finallyを使用)。TFileは新規コードに推奨 — より安全で一貫性がある。

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

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

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

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

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

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

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

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

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

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

ファイルとCSV用TStringList

TStringListはテキストファイルとシンプルなCSVを処理する最も簡単な方法。LoadFromFile/SaveToFileがファイル全体を読み込み/書き込み(1行1アイテム)。CommaTextがカンマ区切り値を分割/結合、DelimitedTextがカスタムDelimiterを使用。Values[]がkey=valueペアを処理(シンプルなINIファイルのようなもの)。Sorted=Trueが自動ソート、Findが二分探索(ソート済みリストでIndexOfより高速)。Duplicatesが重複追加時の動作を制御(dupIgnore、dupAccept、dupError)。複雑なCSV(カンマを含む引用フィールド)には専用CSVパーサーを使用。TStringListは0インデックス。

delphi
uses
  System.Classes;

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

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

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

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

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

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

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

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

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

ストリームとバイナリI/O

TFileStreamは低レベルバイトI/O(Read/Writeバッファ、Positionでシーク)。TBinaryWriter/Readerが型付き値(Int32、Double、String、Boolean)を書き込み/読み取り — 読み取り順序は書き込み順序に一致必要。TStreamReader/Writerがエンコーディング付きテキストを処理(UTF-8、ASCII、Unicode)— 非ASCII文字を含むテキストファイルに使用。すべてのストリームは解放必要(try..finally)。fmCreateが作成/上書き、fmOpenReadが読み取り専用、fmOpenWriteが書き込み用。大きなファイルにはStreamReaderで1行ずつ読み取り(低メモリ)、LoadFromFile(ファイル全体をロード)の代わり。

delphi
uses
  System.Classes, System.SysUtils;

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

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

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

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

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

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

ディレクトリとパス操作

System.IOUtilsがモダンファイル操作用にTPath、TFile、TDirectoryを提供。TPath.Combineがパスを安全に結合(クロスプラットフォーム)。TPath.GetTempFileNameが一意の一時ファイルを作成。TDirectory.GetFilesが検索パターンと再帰検索(soAllDirectories)をサポート。TFile.Copy/Move/Deleteがシンプルなファイル操作。TFileInfoがファイルメタデータ(サイズ、タイムスタンプ)を提供。パスには文字列連結ではなくTPathメソッドを使用(セパレータを正しく処理)。これらのクラスはWindows、macOS、Linux(FireMonkey/FMX)で動作。

delphi
uses
  System.IOUtils, System.SysUtils;

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

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

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

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

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

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

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

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

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

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

INIファイルとJSON

TIniFileがINI設定ファイルを読み書き([括弧]内のセクション、key=value)。ReadString/ReadInteger/ReadBoolがデフォルト値を持つ(キーが見つからない場合に返される)。INIファイルはシンプルで人間が読める設定 — ユーザー設定に適している。構造化データにはJSON(System.JSON)を使用。TJSONObjectがJSONオブジェクトを構築/解析、TJSONArrayが配列用。AddPairがキー値を追加、GetValue<T>が型付き値を取得。ParseJSONValueがJSON文字列を解析。JSONはAPI、複雑な設定、データ交換に理想的。RESTクライアントにはTRESTClientまたはIndyコンポーネントを使用。

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

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

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

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

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

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

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

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

VCLコンポーネント詳細

フォームとコンポーネントのライフサイクル

VCLフォームは厳格なライフサイクルに従う:OnCreate(リソース割り当て、初期化)→ OnShow(フォームが可視化)→ OnActivate → OnResize → OnPaint → ... → OnCloseQuery(クローズをキャンセル可能)→ OnClose → OnDestroy(リソース解放)。リソース管理にはOnCreateとOnDestroyを常にペアに。OnCloseQueryでクローズを防止可能(CanClose := Falseを設定)。Senderがイベントをトリガーしたコンポーネント。コンポーネントは子を所有 — フォームを解放するとすべての子コンポーネントが自動的に解放。

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

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

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

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

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

一般的なVCLコントロール

VCLは豊富なコントロールセットを提供:TEdit(単一行テキスト)、TMemo(複数行テキスト)、TLabel(編集不可テキスト)、TButton、TCheckBox、TRadioButton、TComboBox(ドロップダウン)、TListBox(選択可能リスト)。TStringsが基本コレクション(Lines、ItemsはTStrings)。ItemIndexがアイテムを選択(0ベース、-1 = なし)。ComboBoxスタイル:csDropDown(編集可能)、csDropDownList(読み取り専用)。RadioGroupがItemIndexでラジオボタンをグループ化。Sortedがアイテムを自動ソート。PasswordCharがTEditで入力をマスク。

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

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

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

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

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

StringGridとDBGrid

TStringGridがスプレッドシート風グリッドで表形式データを表示。Cells[Col, Row]が個別セルにアクセス(0インデックス)。FixedRows/FixedColsが非スクロールヘッダーを作成。ColWidths/RowHeightsがサイズをカスタマイズ。goEditing(編集可能セル)、goColSizing(列リサイズ)、goRowSelectなどのオプションがビヘイビアを有効化。OnDrawCellでCanvasを使用したカスタムレンダリングが可能。TDBGridはTDataSource経由でDataSet(TTable、TQuery)に直接接続 — データベースレコードを自動的に表示・編集。データベースデータにはTDBGrid、メモリ内データにはTStringGridを使用。

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

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

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

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

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

TTreeViewとTListView

TTreeViewはTTreeNodeオブジェクトを使用して階層(ツリー)データを表示。AddChildがネストしたノードを作成。Expand(True)が再帰的に展開。GetNextが深さ優先でトラバース、GetNextSiblingがレベルごとにトラバース。BeginUpdate/EndUpdateがパフォーマンスのために変更をバッチ化。TListViewは様々なビュースタイルでアイテムを表示:vsIcon、vsSmallIcon、vsList、vsReport(列)。Captionが最初の列、SubItemsが後続の列を保持。両方ともOnGetNodeData/OnDataイベント経由で大規模データセット用のオーナーデータ(仮想)モードをサポート。

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

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

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

ダイアログと共通コンポーネント

Delphiは標準ダイアログコンポーネントを提供:TOpenDialog/TSaveDialog(ファイル選択)、TOpenPictureDialog(画像プレビュー)、TColorDialog、TFontDialog、TPrintDialog。ExecuteがユーザーがOKをクリックした場合Trueを返す。Filterがファイルタイプパターンを設定('Description|*.ext')。MessageDlgがモーダルメッセージボックスを表示(mtInformation、mtWarning、mtError、mtConfirmation型と[mbYes、mbNo、mbOK、mbCancel]ボタンセット)。InputBox/InputQueryがユーザーテキスト入力を取得。TPageControlがTTabSheetページでタブインターフェースを管理。すべてのダイアログはフォームに配置される非ビジュアルコンポーネント。

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

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

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

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

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

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

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

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

イベント駆動プログラミング

イベントとイベントハンドラ

Delphiのイベントはメソッドポインタ(procedure of object)。TNotifyEventが標準イベント型:procedure(Sender: TObject) of object。イベントはプロパティ — 設計時(オブジェクトインスペクタ)または実行時にハンドラを代入。イベントハンドラを呼ぶ前に常にAssigned()をチェック(未割り当ての場合nilの可能性)。Senderがイベントをトリガーしたオブジェクト。カスタムイベントは'of object'でインスタンスメソッドにバインド。varパラメータ(OnKeyPressのvar Key: Charなど)でハンドラが値を変更可能 — Key := #0で入力を抑制。

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

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

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

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

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

デリゲートとメソッドポインタ

メソッドポインタ('of object')はメソッドアドレスとオブジェクトインスタンスの両方を運ぶ — Selfに対するクロージャ。通常のプロシージャポインタ('of object'なし)はスタンドアロン関数を指す。メソッドポインタはコールバック、ストラテジーパターン、イベントシステムを可能に。Op := Calc.Addで参照を格納、Op(10, 20)の呼び出しでCalcインスタンスのCalc.Addを起動。匿名メソッド(reference to function)はクロージャセマンティクスを持つモダンな代替。メソッドポインタはDelphiのイベント駆動VCL/FMXアーキテクチャの骨格。

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

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

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

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

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

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

匿名メソッドとクロージャ

匿名メソッド(reference to function/procedure)は外側のスコープから変数をキャプチャするインラインクロージャ。'reference to'型はメソッドポインタのモダンな代替 — 参照で変数をキャプチャ、キャプチャされた変数の変更がクロージャに影響。これにより関数型パターンが可能:map/filter/reduce、コールバック、遅延実行。TFunc<T,TResult>とTProc<T>はSystem.SysUtilsのジェネリックエイリアス。匿名メソッドは並列プログラミング(PPL)とモダンDelphiイディオムに不可欠。キャプチャされた変数は宣言スコープより長生き。

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

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

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

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

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

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

メッセージ処理とWindowsメッセージ

VCLはWindowsメッセージ上に構築。'message'指令が特定のメッセージ(WM_LBUTTONDOWN、WM_KEYDOWNなど)を処理。メッセージレコード(TWMMouse、TWMKeyDown)はTMessage上の型付きオーバーレイ。デフォルト処理を許可するため常にinheritedを呼ぶ(メッセージを抑制したい場合を除く)。WndProcがディスパッチ前にすべてのメッセージをインターセプト — 横断的関心事に控えめに使用。PostMessageは非同期(即座に返す)、SendMessageは同期(ハンドラを待つ)。WM_USER + Nでカスタムメッセージを定義。これがWindowsイベント駆動モデルの基盤。

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

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

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

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

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

アプリケーションイベントとアイドル処理

TApplicationEventsがアプリレベルのイベントを一元化:OnIdle(メッセージキューが空時に発生)、OnException(グローバル例外ハンドラ)、OnMinimize/OnRestore、OnHint(ステータスバーヒント)、OnMessage(すべてのWindowsメッセージ)。Done := FalseのOnIdleはタイトループを作成、注意して使用。TTimerがInterval(ミリ秒)でOnTimerを発生 — メッセージベースなのでブロッキング操作中は発生しない。Application.ProcessMessagesが長時間操作中にメッセージキューをポンプ('応答なし'を防止)ただし再入バグを引き起こす可能性。TThread.Queue/SynchronizeがバックグラウンドスレッドからメインスレッドへUI更新をマーシャリング。

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

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

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

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

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

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

FireDACによるデータベースアクセス

接続とクエリの基本

FireDACはDelphiのモダンな汎用データアクセスフレームワークでSQLite、PostgreSQL、MySQL、SQL Server、Oracle、InterBaseなどをサポート。TFDConnectionがデータベース接続を管理(DriverNameとParamsを設定)。TFDQueryがパラメータ付きSQLを実行(:param構文)— SQLインジェクションを防ぐため常にパラメータを使用。ExecSQLはINSERT/UPDATE/DELETE/DDLを実行(結果セットなし)、OpenはSELECTを実行(カーソルを返す)。FieldByName('col').AsString/AsIntegerで値を読み取り。Next/Prev/First/Lastでナビゲート、Eofが終端を示す。FireDACは古いdbExpressとBDEテクノロジを置き換え。

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

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

    Query.Connection := FDConn;

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

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

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

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

TFDTableとLive Bindings

TFDTableはテーブル全体をオープン(SELECT * FROM tablename)— シンプルなCRUDには便利だが大きなテーブルにはTFDQueryより非効率。データセットナビゲーション:First/Next/Prior/Last/MoveBy。Locateがフィールド値で検索(見つかればTrueを返す)。編集:Append/Insert(新規行)またはEdit(既存)、フィールドを設定、Post(コミット)またはCancel(元に戻す)。Filterが表示行を制限(クライアント側)。IndexFieldNamesがレコードをソート。TFDTable/TFDQueryをTDataSourceに接続、さらにTDBGrid/TDBEditへでデータ対応UIが自動化。Live Bindings(FMX)がコントロールとデータフィールドのビジュアルバインディングを提供。

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

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

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

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

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

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

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

トランザクションとバッチ操作

トランザクションは原子性を保証 — すべての操作が成功するか全くなしか。StartTransaction/Commit/Rollbackが関連操作を囲む。明示的トランザクションなしでは、FireDACは各文を自動コミット(バルクインサートに遅い)。Array DML(Execute(count, startAt))がパラメータ化バッチを1往復で送信 — バルクインサートを劇的に高速化(10-100倍)。失敗時にRollbackするため常にトランザクションをtry/exceptで囲む。長いトランザクションには分離レベル(xiReadCommitted、xiRepeatableRead)を検討。接続プーリング(TFDManager)がマルチスレッドパフォーマンスを向上。

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

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

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

ストアドプロシージャとメタデータ

TFDStoredProcがデータベースストアドプロシージャを呼び出し。StoredProcNameとパラメータを設定(ParamType:ptInput、ptOutput、ptInputOutput、ptResult)。ExecProcはカーソルを返さないプロシージャを実行、Openは結果セットを返すものを実行。ストアドプロシージャはパフォーマンスとセキュリティのためサーバー側にビジネスロジックをカプセル化。TFDMetaInfoQueryがデータベーススキーマ(テーブル、列、インデックス、制約)をクエリ — 動的ツール、ORM、スキーマブラウザの構築に便利。MetaInfoKindオプション:mkTables、mkColumns、mkIndexes、mkPrimaryKey、mkForeignKeys。FireDACはオフラインメタデータアクセス用のスキーマキャッシュもサポート。

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

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

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

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

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

FireDACメモリテーブルとLocal SQL

TFDMemTableはインメモリデータセット — キャッシュ、一時データ、データベースなしのユニットテストに最適。FieldDefsでフィールドを定義、CreateDataSet。AppendRecordで行を追加。インデックス、フィルタ、すべてのデータセットナビゲーションをサポート。Local SQL(TFDLocalSQL)で任意のTDataSet(TFDMemTable、TClientDataSet、ODBC経由のExcelも含む)に対してSQLクエリを実行 — メモリテーブルとデータベーステーブル間の結合を可能に。ETL、レポート、オフラインで動作するデータレイヤーの構築に強力。TFDMemTableは永続化のためにバイナリまたはJSONファイルへのロード/セーブも可能。

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

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

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

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

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

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

ジェネリクスと匿名メソッド

ジェネリッククラスとメソッド

ジェネリクス(Delphi 2009で導入)は型安全なコンテナとアルゴリズムを可能に。TStack<T>は任意の型Tで動作 — コンパイラが特殊化バージョンを生成。これにより実行時キャストが不要(TObjectキャストなし)になり型エラーをコンパイル時に捕捉。ジェネリック型パラメータは<T>構文を使用。ジェネリックメソッド、クラス、レコード、インターフェースがすべてサポート。制約(class、constructor、interface)が使用可能な型を制限。RTLはSystem.Generics.CollectionsにTList<T>、TDictionary<TKey,TValue>、TQueue<T>、TStack<T>、TObjectList<T>を提供。

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

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

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

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

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

ジェネリック制約

ジェネリック制約は型パラメータを制限:'class'(クラス型でなければならない)、'constructor'(パラメータなしのCreateコンストラクタが必要 — T.Createを可能に)、'record'(値型でなければならない)、インターフェース名(インターフェースを実装必要)。複数の制約はカンマ区切り。制約によりTのメソッドを呼び出し可能(例:constructor制約でT.Create)。制約なしではTへの代入/比較のみ可能(メソッド呼び出しなし)。型推論により明示的な型パラメータを省略できる場合あり。制約は型安全なフレームワークとORMの構築に不可欠。

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

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

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

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

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

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

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

TList<T>とTDictionary<TKey,TValue>

System.Generics.Collectionsが型安全なコンテナを提供:TList<T>(動的配列)、TDictionary<TKey,TValue>(ハッシュマップ)、TQueue<T>(FIFO)、TStack<T>(LIFO)、TObjectList<T>(オブジェクトを所有 — 自動的に解放)。Sortはデフォルト比較を使用、TComparer<T>.Constructが匿名メソッドでカスタム比較子を作成。FindIndex/述語ベースの検索は匿名関数述語を使用。TryGetValueは見つかった場合Trueと値を出力(例外を回避)。AddOrSetValueは更新または挿入。OwnsObjects := TrueのTObjectList<T>はリスト解放時に含まれるオブジェクトを自動的に解放 — メモリリークを防止。

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

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

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

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

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

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

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

コールバックとしての匿名メソッド

匿名メソッドはDelphiで関数型プログラミングを可能に。'reference to function'型はクロージャ — 外側のスコープから変数をキャプチャ。MapやFilterのような高階関数はパラメータとして関数を取り、簡潔なデータ変換を可能に。TFunc<T,TResult>とTProc<T>は組み込みのジェネリックデリゲート型。クロージャは参照で変数をキャプチャ、後の変更を反映。このパターンは冗長なコールバックインターフェースを置き換え、PPL(並列プログラミングライブラリ)、イベントハンドラ、LINQ風操作に不可欠。匿名メソッドは参照カウントされ自動的に管理。

delphi
uses System.SysUtils;

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

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

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

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

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

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

ジェネリックインターフェースとTComparer

ジェネリックインターフェースは型安全な契約を可能に:IRepository<T>は任意のエンティティ型で動作。参照カウント(TInterfacedObject)と組み合わせることで自動メモリ管理を提供 — インターフェースは参照カウントされ、最後の参照がなくなると解放。TComparer<T>.Constructが匿名比較関数からIComparer<T>を作成 — Sort、BinarySearch、SortedDictionaryで使用。ジェネリックインターフェースはDelphiの依存性注入の基盤(IRepository<TUser>を登録、サービスに注入)。Spring4Dフレームワークはこれを完全なDIコンテナで拡張。ジェネリック制約(class、constructor)がTをインスタンス化可能ことを保証。

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

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

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

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

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

RTTIとリフレクション

拡張RTTIの基本

拡張RTTI(ランタイム型情報)はDelphi 2010で導入、完全なリフレクションを提供:実行時に型、プロパティ、メソッド、フィールドを検査。TRTTIContextがエントリポイント。GetTypeがクラスのTRttiTypeを返す。GetPropertiesが公開プロパティを列挙。GetValue/SetValueがTValue(バリアント風の型)を使用して動的にプロパティ値を読み書き。デフォルトでは'published'メンバーのみがRTTIを持つ(より多くには{$RTTI EXPLICIT ...}指令を使用)。RTTIはシリアライズ(JSON/XML)、ORM、依存性注入、ビジュアルデザイナを駆動。わずかなパフォーマンスオーバーヘッドがあるが強力なメタプログラミングを可能に。

delphi
uses System.RTTI, System.TypInfo;

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

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

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

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

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

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

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

メソッド呼び出しと属性

RTTIはTRttiMethod.Invoke経由でメソッドを動的に呼び出し可能 — 引数をTValue配列として渡す。属性(TCustomAttributeサブクラス)は[Attribute]構文で型、プロパティ、メソッドにメタデータを付加。GetAttributesが実行時にそれらを取得。これにより検証フレームワーク([Required]、[MaxLength])、ORMマッピング([Table]、[Column])、シリアライズ制御([JsonProperty])が可能。属性は強力なメタプログラミング機能 — コンパイラがRTTIに格納、フレームワークがビヘイビアを駆動するために読み取り。RTTI経由のメソッド呼び出しは直接呼び出しより遅いが、スクリプティング、DI、動的ディスパッチに不可欠。

delphi
uses System.RTTI;

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

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

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

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

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

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

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

型の発見と列挙

TRTTIContext.GetTypesがコンパイルされたプログラム内のRTTIを持つすべての型を列挙 — プラグイン発見、ORMモデルスキャン、型ブラウザの構築に便利。FindTypeが修飾名('UnitName.TypeName')で型を検索。TRttiTypeはGetFields(すべてのフィールド)、GetMethods(すべてのメソッド)、GetProperties(公開プロパティ)を提供。TypeKindがクラス、レコード、インターフェース、列挙型などを区別。AsInstance.MetaclassTypeがインスタンス化用のクラス参照を提供。これによりコンポーネントを自動発見して接続するフレームワークが可能。Spring4DとDORMフレームワークはこれを自動ORMマッピングに使用。RTTI列挙は遅い — 繰り返し使用のために結果をキャッシュ。

delphi
uses System.RTTI;

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

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

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

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

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

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

TValueと動的型付け

TValueはDelphiの動的値型 — 任意の型を型情報付きで保持するタグ付きユニオン。From<T>が値をラップ、AsType<T>/AsInteger/AsStringがアンラップ。IsType<T>が型をチェック。TryAsTypeが安全な変換を試行。TValueはRTTI(プロパティ値、メソッド引数)に不可欠で、静的型付け言語で動的型付けを可能に。C#の型情報付き'object'やPythonの動的性質に似ている。TValueはプリミティブ、文字列、オブジェクト、配列、レコードを処理。シリアライザ、スクリプトエンジン、汎用データレイヤーを構築する際に使用。直接型付けよりオーバーヘッドがあるが最大の柔軟性を提供。

delphi
uses System.RTTI;

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

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

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

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

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

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

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

RTTIによるシリアライズ

RTTIは自動シリアライズを可能に — 手動マッピングコードなしでオブジェクトをJSON、XML、または任意の形式に変換。ObjectToJSONが公開プロパティを反復、RTTIで値を読み取り、TJSONObjectを構築。JSONToObjectが逆の処理。このパターンはRESTクライアント、設定システム、ORMレイヤーを駆動。REST.JsonユニットがTJson.ObjectToJsonStringとTJson.JsonToObjectを提供。本番使用には、フィールド名を制御する属性([JsonProperty('name')])を追加し、ネストしたオブジェクト、配列、カスタム型を処理。RTTIベースのシリアライズは手書きマッパーより遅いが保守性がはるかに高い。

delphi
uses System.RTTI, System.JSON;

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

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

インターフェースとCOM

インターフェースの基本と参照カウント

インターフェースは実装なしの契約(メソッドシグネチャ)を定義。TInterfacedObjectが参照カウントを提供 — 最後のインターフェース参照がなくなるとオブジェクトは自動的に解放(Freeを呼ぶ必要なし)。これがDelphiのインターフェースオブジェクトの自動メモリ管理。GUID(['{...}'])がCOM相互運用とInterfaceAs/Supportsチェックを可能に。クラスは複数のインターフェースを実装可能(TShapeはIMovableとIDrawableの両方を実装)。インターフェースプロパティは許可(read/writeメソッドが必要)。参照カウントを機能させるには常にインターフェース型(IMovable)を使用、クラス型(TShape)ではなく。オブジェクトとインターフェース参照の混在は早期解放を引き起こす可能性。

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

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

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

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

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

インターフェースによる依存性注入

インターフェースは依存性注入を可能に — 依存関係(ILogger、IUserDataAccess)をハードコードせずコンストラクタ経由で渡す。これによりTUserServiceを具象実装から疎結合:TConsoleLoggerをTFileLoggerにTUserServiceを変更せずに交換。TUserService自体は参照カウントされない(TObjectを継承、TInterfacedObjectではなく)ので手動でFreeが必要。完全なDIには、インターフェース型で依存関係を解決するコンテナ(Spring4D、DSharp)を使用:Container.RegisterType<ILogger, TConsoleLogger>; Container.Build; Service := Container.Resolve<TUserService>。DIはテスト容易性(モックを注入)、保守性、モジュール性を向上。常に具象ではなく抽象(インターフェース)に依存。

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

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

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

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

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

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

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

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

COM相互運用

COM(Component Object Model)でDelphiがWindowsアプリケーションやライブラリと連携可能。CreateOleObjectが遅延バインディングでCOMオブジェクトを作成(Variant型 — コンパイル時チェックなし、ただしシンプル)。タイプライブラリのインポートが型付きインターフェースを持つ早期バインドユニットを生成(IntelliSense、型チェック、より良いパフォーマンス)。IUnknownが参照カウント用のAddRef/Release/QueryInterfaceを持つ基本COMインターフェース。stdcallがCOM呼び出し規約。CoCreateInstanceが低レベルAPI。一般的なCOM使用:Officeオートメーション(Excel、Word)、ADO(データベース)、シェル統合、WMIクエリ。スレッドでCOM操作の前に常にCoInitializeを呼ぶ。COMオブジェクトはアパートメントスレッド — スレッド間で注意深くマーシャリング。

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

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

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

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

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

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

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

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

Implementsと集約

'implements'指令がインターフェースをプロパティに委譲 — 継承よりコンポジション。TDataServiceがFCache(TMemoryCache)に委譲してICacheを公開。これは継承よりクリーンで、ビヘイビアを組み合わせ可能。Supports()がオブジェクトがインターフェースを実装しているかチェック(内部でQueryInterfaceを使用)。as演算子がチェック付きインターフェースキャストを実行。インターフェース委譲はデコレータパターン(キャッシュをロギングでラップ)、ストラテジーパターン(キャッシュ実装を交換)、関心の分離を可能に。COMのQueryInterfaceが基盤メカニズム — すべてのインターフェースオブジェクトはサポートする任意のインターフェースでクエリ可能。

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

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

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

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

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

弱参照とUnsafe参照

参照カウントは循環参照(親↔子)でメモリリークを引き起こす可能性。[Weak]がサイクルを切断 — 参照を追跡するが参照カウントを増やさず、ターゲットが解放されると自動的にnil化。[Unsafe]は生ポインタ(追跡なし、参照カウントなし)— 最速だが危険(ダングリングポインタ)。親/バック参照、オブザーバーパターン、イベントサブスクリプションに[Weak]を使用。デフォルト(強)参照は参照カウントを増やしオブジェクトを存続。DelphiのARC([Weak]を優先して非推奨)はモバイルでこれを自動処理していた。デスクトップでは、インターフェースは手動参照カウントを使用 — [Weak]がサイクルフリー設計に不可欠。強参照と弱参照を常に正しくペアに。

delphi
type
  TParent = class;
  TChild = class;

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

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

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

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

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

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

マルチスレッドとPPL

TThreadの基本

TThreadはDelphiマルチスレッドの基盤。Executeをオーバーライドしてバックグラウンド処理を記述。Terminatedを定期的にチェックしてグレースフルなキャンセル。TThread.Synchronizeがメインスレッドでコードを実行(ブロッキング — 完了を待つ)、TThread.Queueは非同期(ポストして即座に返す)。バックグラウンドスレッドからUIコントロールには絶対にアクセスしない — 常にSynchronizeまたはQueueを使用。FreeOnTerminate := TrueでExecute終了時にスレッドを自動解放。CreateAnonymousThreadが匿名メソッドからワンショットスレッドを作成 — シンプルなタスクに便利。本番コードでは、より良い構成とエラー処理のため生TThreadよりPPL(TTask)を推奨。

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

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

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

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

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

並列プログラミングライブラリ(PPL)

System.Threadingの並列プログラミングライブラリ(PPL)が高レベル並行性を提供:TTask(ファイアアンドフォーゲット非同期)、TTask.Future<T>(戻り値付き非同期)、並列ループ。タスクは自動的にスレッドプールを使用 — スレッド管理不要。WaitForAll/WaitForAnyが複数タスクを構成。Future.Valueは結果の準備ができるまでブロック(promiseのようなもの)。PPLは生TThreadのモダンな代替 — クリーン、構成可能、async/awaitパターンと統合。タスクは例外をキャプチャし.Valueアクセス時に再スロー、適切なエラー伝播を可能に。タスク間のきめ細かい同期にはTEvent/TCountdownEventを使用。

delphi
uses System.Threading, System.SyncObjs;

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

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

  for var T in Tasks do T.Start;

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

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

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

並列Forとループ

TParallel.Forがスレッドプール全体でループを並列化 — 反復が複数コアで同時に実行。'for'は予約語なので&For(エスケープキーワード)を使用。独立した反復を持つCPUバウンドループは、マルチコアマシンでほぼ線形のスピードアップを提供可能。重要:共有状態(Sumなど)はロック(TCriticalSection)で保護するか、アトミック操作にTInterlocked.Incrementを使用。State.Breakがループを停止(breakのようなもの)。State.ShouldExitがBreakが呼ばれたかチェック。少ない反復や重いI/Oのループは並列化を避ける(スレッドプールが枯渇)。Strideが反復ステッピングを制御。ネストした並列ループは滅多に役立たない — 外側のループが既にコアを飽和。

delphi
uses System.Threading, System.SyncObjs;

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

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

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

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

同期プリミティブ

System.SyncObjsが同期プリミティブを提供:TCriticalSection(ミューテックス — 一度に1スレッドのみ进入)、TEvent(スレッド間シグナル — SetEventが起床、WaitForがブロック)、TMonitor(任意のオブジェクトをロック — Java/C#のWait/Pulse付きモニタのようなもの)、TInterlocked(アトミックIncrement/Decrement/Exchange/CompareExchange — ロックフリー)。TCriticalSectionが最も一般的 — Enter/Leaveを常にtry/finallyでペアに。TEvent.WaitForはwrSignaled、wrTimeout、wrAbandonedを返す。TMonitor.Waitが一時的にロックを解放してブロック、Pulse/PulseAllが待機者を起床。TInterlockedはシンプルなカウンタに最速 — ロックオーバーヘッドなし。適切なプリミティブを選択:排他アクセスにCriticalSection、シグナリングにEvent、アトミックカウンタにInterlocked。

delphi
uses System.SyncObjs;

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

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

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

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

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

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

スレッドプールとAsync/Awaitパターン

TThreadPoolがワーカースレッドのプールを管理 — スレッドを再利用することで作成オーバーヘッドを回避。ワークロードに基づいて最小/最大スレッドを設定(CPUバウンド:〜コア数、I/Oバウンド:より多く)。TTask.RunがCreate+Startのショートハンド。ContinueWithがタスクをチェーン — 前のタスク完了後に実行、パイプラインを可能に。Task.Status(Created、WaitingToRun、Running、Completed、Canceled、Faulted)がライフサイクルを追跡。ICancellationが協調的キャンセルを可能に — 長いタスクで定期的にIsCancelledをチェック。真のasync/awaitには、Delphiは言語レベルのawaitを持たないが、TTask.Future + .Valueが同等のセマンティクスを提供。OmniThreadLibrary(OTL)はPPL上に構築されたより高レベルな抽象化(パイプライン、メッセージパッシング)を提供。

delphi
uses System.Threading, System.SyncObjs;

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

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

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

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

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

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

Indyによるネットワークプログラミング

TCPクライアントとサーバー(Indy)

Indy(Internet Direct)はDelphiにバンドルされたネットワーキングライブラリ。TIdTCPClientがサーバーに接続 — WriteLn/ReadLnで行ベースプロトコル、Write/Readでバイナリ。ConnectTimeoutがハングを防止。TIdTCPServerが接続をリッスン — OnExecuteがクライアントごとのスレッドで実行(AContextが各接続を表す)。Indyはブロッキングソケットを使用(シンプルなモデル — コールバックなし)、サーバーハンドラはワーカースレッドで実行。切断は常にグレースフルに処理。高性能サーバーにはICS(オーバーラップI/O)またはSynapseを検討。Indyコンポーネントは非ビジュアル — フォームにドロップまたはコードで作成。Active := Trueでリッスン開始。DefaultPortでリッスンポートを設定。

delphi
uses IdTCPClient, IdTCPServer, IdContext;

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

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

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

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

HTTPクライアント(TIdHTTP)

TIdHTTPはIndyのHTTPクライアント — GET、POST、PUT、DELETE、ヘッダー、クッキー、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リダイレクトを自動的に追跡。

delphi
uses IdHTTP, IdSSLOpenSSL, System.JSON;

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

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

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

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

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

SMTPメール(TIdSMTP)

TIdSMTPがSMTPサーバー経由でメールを送信。TIdMessageがメールを表す(From、Recipients、Subject、Body)。Gmail/Office365にはTLS(Port 587、utUseExplicitTLS)またはSSL(Port 465、utUseImplicitTLS)を使用。Gmailは2FA有効化で'アプリパスワード'(通常のパスワードではなく)が必要。TIdAttachmentFileがファイル添付を追加。HTMLメールにはContentType := 'text/html'を設定。マルチパート(HTML + プレーンテキスト + 添付)にはTIdMessageBuilderHTMLを使用。一般的なポート:25(暗号化なし/リレー)、465(SSL)、587(STARTTLS)。Connect/Sendを常にtry/finallyで囲みDisconnectを保証。メール受信にはTIdPOP3またはTIdIMAP4を使用。

delphi
uses IdSMTP, IdMessage, IdSSLOpenSSL, IdExplicitTLSClientServerBase;

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

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

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

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

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

UDPと生ソケット

UDPはコネクションレス — ハンドシェイクなし、配信保証なし、ただしTCPより高速。TIdUDPClient.Sendがデータグラムを送信、ReceiveStringがタイムアウト付きでレスポンスを待つ。BroadcastEnabledが255.255.255.255(LAN上のすべてのデバイス)に送信 — サービス発見に便利。TIdUDPServer.OnUDPReadがデータグラムを受信、ABinding.PeerIP/PeerPortが送信者を識別。UDPはDNS、SNMP、ゲーム状態更新、ストリーミングメディア、発見プロトコルに理想的。UDP上の信頼性には、アプリケーションレベルでACK/リトライを実装。TIdBytesがIndyのバイト配列型 — 変換にはBytesToString/ToBytesを使用。生ソケット制御(生IPパケット、カスタムプロトコル)にはWinSock2ユニットまたはSynapseライブラリを使用。

delphi
uses IdUDPClient, IdUDPServer, IdSocketHandle;

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

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

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

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

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

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

FTPとRESTクライアント

TIdFTPがFTPクライアント機能を提供 — Connect、List、Put(アップロード)、Get(ダウンロード)、MakeDir、ChangeDir。パッシブモード(Passive := True)がNAT/ファイアウォールを通過。UseTLSがFTPをセキュア化(FTPS)。SFTP(SSHベース)にはサードパーティライブラリ(libssh2、SecureBlackbox)を使用。TRESTClient/TRESTRequest/TRESTResponseは組み込みのRESTコンポーネント(OpenSSL依存なし)— モダンなAPI消費に理想的。ResourceはAddUrlSegmentで埋める{param}プレースホルダを使用。Executeがリクエストを送信、RESTResponse.Contentがボディを保持、JSONValueがJSONを自動解析。RESTコンポーネントはOAuth2、ベーシック認証、カスタムオーセンティケータをサポート。高性能RESTにはTNetHTTPClient(より軽量)または最大制御のためIndyのTIdHTTPを検討。

delphi
uses IdFTP, IdFTPCommon, IdExplicitTLSClientServerBase;

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

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

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

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

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

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

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

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

DLLとBPLパッケージ

DLLの作成と使用

DLL(Dynamic Link Library)はアプリケーション間でコードを共有。'program'ではなく'library'キーワードでDLLをビルド。'exports'が外部呼び出し元が利用可能な関数をリスト。stdcallが標準Windows呼び出し規約(C/C++、VB、C#互換)。静的インポート(external)はコンパイル時にリンク — DLLは実行時に存在必要。動的ロード(LoadLibrary/GetProcAddress)は実行時にロード — プラグインとオプション機能を可能に。FreeLibraryがDLLをアンロード。PChar(PWideChar)がDLLエクスポートの標準文字列型(共有メモリ、Delphi固有の型なし)。Delphiの文字列、オブジェクト、インターフェースは絶対に直接エクスポートしない — これらはDelphi内部。Delphi間の文字列共有にはShareMemユニットを使用(BorlndMM.dllが必要)。

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

uses
  System.SysUtils, System.Classes;

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

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

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

begin
end.

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

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

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

インターフェース経由のオブジェクト共有

DLL境界を越えたオブジェクト共有は注意が必要 — Delphiクラスは直接エクスポート不可(異なるメモリマネージャ、異なるRTTI)。解決策:GUID付きインターフェースを使用。DLLがファクトリ関数(CreatePlugin)をエクスポートしIPluginを返す。ホストアプリは同じインターフェース(同じGUID!)を定義しファクトリを呼ぶ。インターフェース参照カウントがクリーンアップを自動処理。文字列にはPCharを使用(Delphi stringではなく)しメモリマネージャの競合を回避。これがプラグインアーキテクチャパターン — DLLを動的にロード、ファクトリ経由でプラグインを作成、インターフェース経由で通信。完全なプラグインシステムには、Delphiのプラグインフレームワークを検討、またはRTLを共有し直接クラス共有を許可するパッケージ(BPL)を使用。

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

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

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

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

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

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

exports CreatePlugin;

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

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

BPLパッケージ(Borland Package Library)

BPL(Borland Package Library)はDelphi固有の共有ライブラリ — Delphi RTLを共有し、直接クラス/オブジェクト共有を可能に(DLLと異なる)。'package'キーワードでビルド。ランタイムパッケージはEXEサイズを削減(共有コードは.bplファイル)し、ホットスワップ可能なモジュールを可能に。LoadPackage/UnloadPackageがBPLを動的にロード — GetClassが名前で登録されたクラスを検索。RegisterClass/UnRegisterClassがクラスを発見可能に。BPLはDelphi RTL BPL(rtl.bpl、vcl.bpl)のデプロイが必要。BPLの使用:プラグインアーキテクチャ(Delphi型を直接共有)、モジュラーアプリケーション(機能をオンデマンドでロード)、メモリ削減(共有コードを1回ロード)。クロス言語共有にはDLLを使用、DelphiのみならBPLがより強力。

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

requires
  rtl,
  vcl;

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

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

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

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

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

境界を越えたメモリ管理

DLLの最大の落とし穴:あるモジュールで割り当てられたメモリを別のモジュールで解放すること。各モジュールは独自のメモリマネージャを持つ — 混在はヒープ破損とクラッシュを引き起こす。解決策:(1) ShareMem — BorlndMM.dllを共有、ただしそのDLLのデプロイが必要。(2) 呼び出し元割り当てパターン — 呼び出し元がバッファを提供、DLLが埋める(最も安全、言語非依存)。(3) SimpleShareMem/FastMM — モダンな共有メモリマネージャ(FastMMはDelphi 2006以降デフォルト)。(4) コールバックベース解放 — DLLが解放関数を提供。PCharリターンにはStrNew/StrDisposeを使用(Windows API、共有)。本番Delphi間にはBPL(共有RTL)またはSimpleShareMemを使用。クロス言語には常に呼び出し元割り当てパターンを使用。共有メモリマネージャなしでDelphiの文字列/オブジェクト/インターフェース型をDLL境界を越えて渡さない。

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

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

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

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

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

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

リソースファイルと埋め込み

リソースファイルはバイナリデータ(画像、アイコン、サウンド、文字列、バージョン情報)をEXE/DLLに埋め込み — 外部ファイル不要。.rcスクリプトを作成、brcc32でコンパイル(またはIDEに自動コンパイルさせる)。{$R file.res}でリンク。TResourceStreamがRCDATAリソースをストリームとして読み取り。LoadIcon/LoadStringが特定のリソース型にWindows APIを使用。リソースは実行時に読み取り専用だが、すべてを1ファイルに保持(デプロイに最適)。一般的な使用:アプリケーションアイコン、スプラッシュスクリーン画像、デフォルト設定、WAVサウンド、バージョン情報(ファイルプロパティダイアログ)、ローカライズされた文字列。大きなデータには埋め込み前に圧縮を検討。リソースIDは名前(文字列)または数値。RT_RCDATAが汎用バイナリリソース型。

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

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

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

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

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

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

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

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

デバッグとパフォーマンスチューニング

デバッガとブレークポイント

DelphiのIDEデバッガは強力:ガターをクリックしてブレークポイントを設定。条件付きブレークポイントは式がtrueの場合のみブレーク(例:i > 100)。ログ/トレースブレークポイントは停止せずにメッセージをログ — ループの監視に最適。asm int 3 endがコードにハードブレークポイントを作成(CPUトラップ)。OutputDebugStringがイベントログウィンドウ(とDebugViewツール)にログ。Assertがデバッグビルドで条件をチェック({$C-}またはリリースでアサーションオフで無効化)。DebugHookはIDEで実行時に非ゼロ。コールスタックウィンドウが呼び出しチェーンをトレース、スレッドウィンドウがすべてのスレッドを検査、ローカル変数が現在のスコープを表示。RTL/VCLソースコードにステップインするには'Use Debug DCUs'を有効化。

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

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

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

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

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

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

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

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

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

例外処理とスタックトレース

Delphi例外:try/exceptがエラーをキャッチ、try/finallyがクリーンアップを保証。例外クラスは階層を形成:Exception → EDivByZero、EAccessViolation、EListError、EAbort(サイレント)など。'on E: ExceptionType do'が特定の型をキャッチ、基底'on E: Exception do'がすべてをキャッチ。'raise;'が現在の例外を再スロー(スタックトレースを保持)。EAbort(またはAbortプロシージャ)がサイレント例外をスロー(ダイアログなし)。TApplicationEvents.OnExceptionがグローバルハンドラ — 未処理例外をキャッチ。スタックトレースにはJCL(JclDebug)またはMadExcept/ExceptionHunterを使用 — コールスタック、レジスタダンプ、クラッシュレポートのメール送信もキャプチャ。事後デバッグのため常に例外をログ。本番で例外を暗黙に飲み込まない。

delphi
uses
  System.SysUtils, System.Diagnostics;

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

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

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

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

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

プロファイリングとパフォーマンス

TStopwatchが高精度タイマー(QueryPerformanceCounterを使用)。最適化前に常にベンチマーク — 推推測しない。ReportMemoryLeaksOnShutdown := Trueがプログラム終了時にリークをキャッチ(デバッグビルド)。一般的なDelphiパフォーマンスの落とし穴:(1) ループ内の文字列連結はコピーを作成 — TStringBuilderまたは事前割り当てを使用。(2) ループ内のSetLengthは再割り当て — サイズを1回設定。(3) 値渡しの文字列/配列はコピー — 読み取り専用パラメータには'const'を使用。(4) TStringList.Sorted + FindはO(log n)、未ソートのIndexOfはO(n)。(5) TList<T>.Addは償却O(1)だが先頭へのInsertはO(n)。深いプロファイリングにはSampling Profiler(無料)、AQTime、またはGpProfileを使用 — コード変更なしでホットスポットを特定。時間の80%を取る20%のコードを最適化。

delphi
uses System.Diagnostics;

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

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

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

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

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

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

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

メモリ管理とリーク

メモリ管理はDelphiの最大のバグ源。ルール#1:すべてのCreateに一致するFreeが必要。try/finallyを厳格に使用。自動管理にはインターフェース(TInterfacedObject + 参照カウント)を使用 — Free不要。OwnsObjects := TrueのTObjectList<T>が含まれるオブジェクトを自動的に解放。ReportMemoryLeaksOnShutdown := Trueが終了時にリークしたオブジェクトのリストをダイアログ表示(デバッグのみ)。FastMM(デフォルトメモリマネージャ)のFullDebugModeが割り当てスタックトレース付きでリークをファイルにログ — リーク追跡に不可欠。一般的なリークパターン:try/finallyの欠落、イベントハンドラの未削除、循環参照([Weak]で修正)、未解放のスレッド、finalizationで未解放のグローバルオブジェクト。ユニットのfinalization部はシャットダウン時に実行 — グローバルクリーンアップに使用。

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

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

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

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

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

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

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

コード品質とテスト

DUnitXがモダンなユニットテストフレームワーク(DUnitを置き換え)。[TestFixture]がテストクラスをマーク、[Test]がテストメソッドをマーク、[Setup]/[TearDown]が各テストの前後に実行。[TestCase]がインラインデータでテストをパラメータ化。Assert.AreEqual/IsTrue/WillRaiseが結果を検証。テスト駆動開発(TDD):テストを先に書き、次にコード。テストはリグレッションをキャッチし期待されるビヘイビアを文書化。Delphi Mocks(またはSpring4Dモッキング)がインターフェースからモックオブジェクトを作成 — Setup.Expectが期待を定義、VerifyAllが満たされたかチェック。モッキングはユニットを隔離(データベース、ネットワーク、ファイルシステムをモック)に不可欠。ビジネスロジックの高いカバレッジを目指す。CI(継続的インテグレーション)でテストを実行し早期にリグレッションをキャッチ。統合テストはコンポーネントが連携することを検証、ユニットテストは個々のユニットを隔離して検証。

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

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

    [TearDown]
    procedure TearDown;

    [Test]
    procedure TestAdd;

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

    [Test]
    procedure TestDivideByZero;
  end;

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

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

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

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

ジェネリクスとコレクション

ジェネリッククラス宣言

ジェネリクスでキャストなしの型安全なコンテナを記述可能。型名の後に<T>で宣言。コンパイラが使用された型ごとに特殊化バージョンを生成。ジェネリック型の動的配列にはarray ofの代わりにTArray<T>を使用。

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

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

TDictionaryの使用

TDictionary<K,V>がジェネリックハッシュマップ。Addは重複キーでスロー、OrAddはアップサート。TryGetValueは見つからない場合例外ではなくfalseを返す。辞書は常に解放 — デフォルトでオブジェクトを所有しない。

delphi
uses
  System.Generics.Collections;

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

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

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

比較子付きTList

TList<T>.SortはIComparer<T>を使用。TComparer<T>.Constructが匿名関数を比較子にラップ。BinarySearchは同じ比較子でソートされたリストが必要。AddRangeはオープン配列または別のリストを受け入れる。

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

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

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

ジェネリック制約

制約はどの型が代入可能かを制限:'class'(参照型)、'record'(値型)、'constructor'(パラメータなしコンストラクタ)、または特定の祖先クラス。複数の制約はカンマ区切り。'constructor'なしではT.Createを呼べない。

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

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

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

TObjectDictionaryによるオブジェクト所有権

TObjectDictionary<K,V>はTDictionaryを所有権で拡張。[doOwnsValues]、[doOwnsKeys]、または両方を渡す。Remove/Clear/Free時に所有オブジェクトが自動的に解放 — オブジェクトコレクションでメモリリークを防止。

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

匿名メソッドとクロージャ

基本的な匿名メソッド

匿名メソッドはインライン関数参照。TFunc<...>は関数用、TProc<...>はプロシージャ用。外側のスコープから変数をキャプチャ(クロージャ)。変数に代入可能、パラメータとして渡せる。

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

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

変数をキャプチャするクロージャ

キャプチャされた変数はヒープ割り当てで匿名メソッドが存続する限り生きる。MakeMultiplierの各呼び出しは独自のFactorをキャプチャ — クロージャは独立。これがファクトリと部分適用の仕組み。

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

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

高階関数

'reference to'が匿名メソッドと互換のプロシージャ型を宣言。Applyは高階関数 — 関数を引数に取る。これによりmap/filter/reduceパターンが可能。動的配列にはTArray<Integer>を使用。

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

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

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

クロージャによるイベントハンドラ

匿名メソッドは従来のメソッドベースのイベントハンドラを置き換え可能、フィールドなしでコンテキストをキャプチャ。ワンオフハンドラとボイラープレート削減に便利。キャプチャされたCaptionはOnClickが保持するクロージャ参照と共に存続。

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

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

匿名メソッド付きTThread

CreateAnonymousThreadがクロージャをスレッドでラップ — ファイアアンドフォーゲットのバックグラウンド処理。UI更新をメインスレッドにマーシャリングするにはTThread.Queue(またはSynchronize)を使用。ワーカースレッドからUIコントロールには直接触れない。

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

属性とRTTI

カスタム属性宣言

属性はTCustomAttributeを継承するクラス。型、フィールド、メソッド、プロパティに[AttrName(...)]で適用。コンパイラがRTTIに埋め込む。コンストラクタパラメータが属性の引数になる。

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

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

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

RTTIによる属性の読み取り

TRTTIContextがRTTIのエントリポイント。GetTypeがクラスのTRttiTypeを返す。GetAttributesが適用されたすべての属性を返す。プロパティを読むために属性型にキャスト。RTTIには{$M+}でコンパイルされたユニットまたはTPersistent派生のクラスが必要。

delphi
uses
  System.Rtti;

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

フィールドとメソッドのRTTI

GetFieldsがすべてのpublic/publishedフィールドを返す。SetValue/GetValueが名前で動的フィールドアクセスを提供 — シリアライザとORMに便利。GetMethodsが継承を含むすべてのメソッドを返す。RTTIは直接呼び出しより遅い。

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

プロパティRTTIと呼び出し

GetPropertiesが公開プロパティを返す。IsReadable/IsWritableがアクセサをチェック。GetValue/SetValueはプロパティでも動作。TypeKind(tkInteger、tkString、tkClassなど)で各型を適切に処理可能。これがほとんどのDelphiシリアライザの仕組み。

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

名前によるメソッド呼び出し

GetMethodが名前でメソッドを検索(大文字小文字区別)。InvokeがTValue配列引数で動的に呼び出し。TValueは任意の型のバリアント風ラッパー。プラグインシステム、スクリプティング、遅延バインディングに便利。TValueを返す — AsInteger、AsStringなどで変換。

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

インターフェース詳細

インターフェース宣言と実装

インターフェースは実装なしの契約を定義。GUID(オプションだが推奨)が'as'キャストとSupports()を可能に。TInterfacedObjectが参照カウントを提供。すべてのインターフェースメソッドを実装必要('abstract'エスケープなし)。インターフェースのプロパティにはアクセサメソッドが必要。

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

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

参照カウントとメモリ

インターフェース参照は参照カウントされる。最後のインターフェース参照がスコープを抜けるとオブジェクトは解放。同じインスタンスへのオブジェクト参照とインターフェース参照を絶対に混在させない — インターフェースの参照カウントがオブジェクトポインタがまだ指している間に解放する。1つの所有権モデルを選ぶ。

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

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

インターフェース継承と複数インターフェース

インターフェースは複数の親から継承可能。クラスは複数のインターフェースを実装可能。メソッド解決節(method = interface.method)が複数のインターフェースが同じメソッドを宣言する場合の競合を解決。実行時にインターフェースを問い合わせるには'as'またはSupports()を使用。

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

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

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

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

Supportsとasキャスト

Supports()がオブジェクトがインターフェースを実装しているかチェック — booleanを返し、オプションでインターフェースを返す。'as'キャストは同じことをするが失敗時にEInvalidCastをスロー。Supports()はオブジェクトとインターフェース参照の両方で動作。インターフェースにGUIDが必要。

delphi
uses
  System.SysUtils, System.TypInfo;

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

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

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

依存性注入パターン

依存関係をインターフェースとして渡す — モッキング、実装の交換、テスト容易性を可能に。クラスは具象型ではなく抽象(ILogger)に依存。これがSpring4DのようなDIコンテナの基盤。インターフェース所有権はロガーがサービスが参照を保持する限り存続することを意味。

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

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

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

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

メモリ管理詳細

Try-finallyパターン

割り当ては常にtry-finallyでFreeとペアに。複数リソースにはfinallyブロックをネスト。FreeAndNil(Freeの代わり)は変数もクリア — use-after-freeの検出に便利。Freeはnilで安全 — Assignedを先にチェック不要。

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

インターフェースベースの所有権

TInterfacedObject + インターフェース参照 = 自動クリーンアップ。インターフェースがスコープを抜けるとデストラクタが実行。これがDelphiのRAII — try-finallyのボイラープレートなしで保証されたクリーンアップのためにリソースをインターフェースオブジェクトでラップ。

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

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

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

弱参照

弱参照が参照サイクルを切断。[Weak]なしでは、互いにインターフェース参照を保持する2つのオブジェクトは決して解放されない(サイクル)。TComponentは弱参照用の組み込みFreeNotificationメカニズムを持つ。[Weak]属性はRTTIを必要としインターフェースとクラスフィールドで動作。

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

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

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

レコードとオブジェクト

レコードは値型(スタック、代入時にコピー)— メモリ管理不要。クラスは参照型(ヒープ、解放必要)。小さな不変データ(ポイント、日付、金額)にはレコードを使用。ポリモーフィックまたは大きなオブジェクトにはクラスを使用。モダンDelphiのレコードはメソッドと演算子を持てる。

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

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

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

メモリリーク検出

ReportMemoryLeaksOnShutdownが終了時にリークしたオブジェクトのリストをダイアログ表示。FastMM(デフォルトメモリマネージャ)がリーク、二重解放、use-after-freeを検出。本番にはリークをファイルにログ。開発中に定期的にリークチェックを実行 — 導入時に修正する方が簡単。

delphi
uses
  System.ReportMemoryLeaksOnShutdown;

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

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

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

FireMonkey(FMX)

クロスプラットフォームフォームの基本

FMXフォームはクロスプラットフォーム(Windows、macOS、iOS、Android、Linux)。同じコード、異なるネイティブレンダラー。Vcl.*の代わりにFMX.*ユニットを使用。コントロールはベクターベース(完全にスケール)。スタイルがテーマを置き換え — ビジュアル外観はデータ駆動。

delphi
unit MainForm;

interface

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

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

var
  FormMain: TFormMain;

implementation

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

end.

レイアウトと配置

FMXはAlign(Client、Top、Bottom、Left、Right、None)とMargins/Paddingでレイアウト。TFlowLayoutがCSS flexboxのように子を配置。TGridLayoutがグリッドを作成。解像度非依存のスケーリングにはTScaleBoxを使用。レイアウト自体がコントロール — ネスト可能。

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

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

スタイルとスタイリング

スタイルは.fsfまたは.styleファイルに保存されたビジュアルリソース(ブラシ、フォント、エフェクト)のコレクション。StyleLookupがコントロールの名前付きスタイルを選択。TStyleManagerが実行時にグローバルスタイルを切り替え。FMXスタイルはベクター — 任意のDPIにスケール。スタイルデザイナがスタイルをビジュアルに編集。

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

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

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

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

エフェクトとアニメーション

エフェクト(Glow、Shadow、Blur、Reflection)はコントロールに親付けされる非ビジュアルコンポーネント。アニメーション(TFloatAnimation、TColorAnimation、TPathAnimation)が時間経過でプロパティをアニメーション。Parentをターゲットコントロールに設定。Trigger/Startで開始。すべてGPUアクセラレーテッド — すべてのプラットフォームでスムーズ。

delphi
uses
  FMX.Effects, FMX.Ani;

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

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

プラットフォームサービス

プラットフォームサービスがOS固有の機能を抽象化。SupportsPlatformServiceで問い合わせ — サポートされていないプラットフォームではfalseを返す。使用前に常にチェック。一般的なサービス:クリップボード、ダイアログ、仮想キーボード、デバイス情報、スクリーン。このパターンで{$IFDEF}ブロックなしでコードをクロスプラットフォームに保つ。

delphi
uses
  FMX.Platform;

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

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

データベース(FireDAC)

接続セットアップ

TFDConnectionが中央FireDACオブジェクト。DriverName(SQLite、MSSQL、MySQL、PostgreSQL、Oracleなど)とParamsを設定。接続定義は再利用のため.iniファイルに保存可能。解放前に常にConnected := Falseを設定。接続プーリングにはTFDManagerを使用。

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

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

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

クエリ実行

SELECTにはOpenを使用(カーソルを返す)、INSERT/UPDATE/DELETEにはExecSQLを使用(影響行数を返す)。常にパラメータを使用 — 値をSQLに連結しない(インジェクションリスク)。ParamByNameは大文字小文字を区別しない。FieldByNameが名前で列にアクセス。Eof/Nextで行を反復。

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

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

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

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

トランザクション

StartTransaction/Commit/Rollbackが原子的操作を囲む。任意の文が失敗した場合、Rollbackがすべての変更を元に戻す。ネストしたトランザクションはセーブポイントを使用(部分ロールバック)。エラー伝播後のロールバックのために常にtry-except-raiseで囲む。トランザクションなしでは各文が自動コミット。

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

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

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

TFDTableとライブデータ

TFDTableはテーブル上のライブで編集可能なカーソル。Edit/Postが現在の行を変更。Append/Postが挿入。Deleteが現在の行を削除。変更は直接データベースに。順序付けにはIndexFieldNamesを使用。複雑なクエリには代わりにTFDQueryを使用。

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

    // edit current row
    Table.Edit;
    Table.FieldByName('age').AsInteger := 31;
    Table.Post;

    // insert new row
    Table.Append;
    Table.FieldByName('name').AsString := 'Bob';
    Table.FieldByName('age').AsInteger := 25;
    Table.Post;

    // delete current row
    Table.Delete;
  finally
    Table.Free;
  end;
end;

バッチ更新とキャッシュモード

CachedUpdatesモードが変更をメモリにバッファ — ApplyUpdatesで一度に適用。行ごとの更新よりバルク操作に高速。CancelUpdatesがバッファを破棄。Statusが行ごとの変更タイプを表示。切断シナリオとラウンドトリップ削減に便利。

delphi
Query.CachedUpdates := True;
Query.Open;

// make many changes locally
while not Query.Eof do
begin
  Query.Edit;
  Query.FieldByName('status').AsString := 'processed';
  Query.Post;
  Query.Next;
end;

// apply all changes in one transaction
FDConn.StartTransaction;
try
  Query.ApplyUpdates;
  FDConn.Commit;
except
  FDConn.Rollback;
  Query.CancelUpdates;
  raise;
end;

// inspect change log:
Query.Status;  // TUpdateStatus (usModified, usInserted, usDeleted)
26

RESTとHTTP

TRESTClientの基本

TRESTClientがベースURLを保持。TRESTRequestがリクエストを構築(メソッド、リソース、パラメータ)。TRESTResponseが結果を保持。URLセグメント({id})はAddUrlSegmentで置換。StatusCode/ContentがHTTPレスポンスを提供。作成の逆順で解放。

delphi
uses
  REST.Client, REST.Types;

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

    Request.Execute;

    if Response.StatusCode = 200 then
      Writeln(Response.Content);
  finally
    Response.Free;
    Request.Free;
    Client.Free;
  end;
end;

JSON解析

System.JSONがTJSONObject、TJSONArray、TJSONValueを提供。ParseJSONValueが文字列を解析(TJSONValueを返す — 必要に応じてキャスト)。GetValue<T>が型付き値を読み取り。AddPair/AddElementがJSONを構築。すべてのJSONオブジェクトは解放必要 — 親に所有されている場合のみ参照カウントされる。

delphi
uses
  System.JSON;

var
  JSON: TJSONObject;
  Arr: TJSONArray;
  Item: TJSONObject;
  I: Integer;
begin
  // parse
  JSON := TJSONObject.ParseJSONValue('{"name":"Alice","age":30}') as TJSONObject;
  try
    Writeln(JSON.GetValue<string>('name'));
    Writeln(JSON.GetValue<Integer>('age'));
  finally
    JSON.Free;
  end;

  // build
  JSON := TJSONObject.Create;
  try
    JSON.AddPair('name', 'Bob');
    JSON.AddPair('scores', TJSONArray.Create(90, 85, 92));
  finally
    JSON.Free;
  end;
end;

DataSnapによるRESTサーバー

DataSnapがDelphiメソッドをRESTエンドポイントとして自動的に公開。メソッド名がURLセグメントになる。パラメータはURLセグメントまたはPOSTボディにマップ。TJSONObject/TJSONArrayが標準の戻り値の型。HTTP動詞を指定するには[httppost]などの属性を適用。基底クラスとしてTDSServerModuleを使用。

delphi
// ServerContainerUnit1.pas
type
  TServerMethods1 = class(TDSServerModule)
    function GetUsers: TJSONArray;
    function GetUser(id: Integer): TJSONObject;
    [httppost] function CreateUser(Data: TJSONObject): TJSONObject;
  end;

function TServerMethods1.GetUsers: TJSONArray;
begin
  Result := TJSONArray.Create;
  // ... populate from DB ...
end;

// Access via URL:
//   GET  http://localhost:8080/datasnap/rest/TServerMethods1/GetUsers
//   GET  http://localhost:8080/datasnap/rest/TServerMethods1/GetUser/42
//   POST http://localhost:8080/datasnap/rest/TServerMethods1/CreateUser

低レベル制御用Indy HTTP

TIdHTTP(Indy)がHTTPの完全な制御を提供 — ヘッダー、クッキー、リダイレクト、タイムアウト。TRESTClientより冗長だがより柔軟。HTTPSにはSSL IOHandler(TIdSSLIOHandlerSocketOpenSSL)を割り当て。本番用にReadTimeout/ConnectTimeoutを設定。Indyは同期 — 非同期にはTThreadでラップ。

delphi
uses
  IdHTTP, IdGlobal;

var
  Http: TIdHTTP;
  Response: string;
  PostData: TStringStream;
begin
  Http := TIdHTTP.Create(nil);
  try
    Http.Request.ContentType := 'application/json';
    Http.Request.CustomHeaders.AddValue('Authorization', 'Bearer token123');

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

    // POST
    PostData := TStringStream.Create('{"name":"Alice"}', TEncoding.UTF8);
    try
      Response := Http.Post('https://api.example.com/users', PostData);
    finally
      PostData.Free;
    end;

    Writeln(Http.ResponseCode);  // 200, 404, etc.
  finally
    Http.Free;
  end;
end;

タスクによる非同期HTTP

REST呼び出しをTTask.RunでラップしてUIスレッドのブロックを回避。UI更新をTThread.Queue(非同期)またはTThread.Synchronize(同期)でマーシャリング。オブジェクトのライフタイムに注意 — リクエストはタスクより長生き必要。組み込みの非同期サポートにはTRESTRequest.ExecuteAsyncを検討。

delphi
uses
  System.Threading, REST.Client;

var
  Request: TRESTRequest;
begin
  Request := TRESTRequest.Create(nil);
  try
    Request.Client := TRESTClient.Create('https://api.example.com/users');
    Request.Client.Owner := Request;

    TTask.Run(
      procedure
      begin
        Request.Execute;  // background thread

        TThread.Queue(nil,
          procedure
          begin
            // update UI on main thread
            Memo1.Lines.Text := Request.Response.Content;
          end);
      end);
  finally
    // don't free Request here — task may still be running
  end;
end;
27

マルチスレッド(並列)

TThreadの基本

TThreadをサブクラス化しExecuteをオーバーライド。Create(False)は即座に開始、Create(True)は.Startが必要。FreeOnTerminate := Trueで自動解放 — このようなスレッドにはFreeを絶対に呼ばない。グレースフルなシャットダウンのためにTerminatedを定期的にチェック。ExecuteからUIには絶対に触れない — Synchronize/Queueを使用。

delphi
type
  TWorker = class(TThread)
  protected
    procedure Execute; override;
  public
    constructor Create;
  end;

procedure TWorker.Execute;
var
  I: Integer;
begin
  for I := 1 to 100 do
  begin
    if Terminated then Exit;
    // ... work ...
    Sleep(50);
  end;
end;

constructor TWorker.Create;
begin
  inherited Create(False);  // False = start immediately
  FreeOnTerminate := True;  // auto-free on completion
end;

TTaskとフューチャー

System.ThreadingのITask/IFuture<T>はTThreadより高レベル。フューチャーは型付き値を返す — .Valueは結果の準備ができるまでブロック。タスクは参照カウント(手動Freeなし)。複数タスクの調整にはTTask.WaitForAll / WaitForAnyを使用。生TThreadより使いやすい。

delphi
uses
  System.Threading;

var
  Task: ITask;
  Future: IFuture<string>;
begin
  // fire-and-forget task
  Task := TTask.Create(
    procedure
    begin
      // ... background work ...
    end);
  Task.Start;

  // future — returns a value
  Future := TTask.Future<string>(
    function: string
    begin
      Sleep(1000);
      Result := 'computed value';
    end);

  // ... do other work ...

  Writeln(Future.Value);  // blocks until ready
end;

並列forループ

TParallel.ForがCPUコア全体でループ反復を並列実行。共有状態を同期必須(TCriticalSectionまたはTInterlockedを使用)。反復順序は非決定的。break/continueにはTLoopStateを使用。CPUバウンド作業に高速、 trivialな反復にはオーバーヘッドで遅い。

delphi
uses
  System.Threading, System.SyncObjs;

var
  Total: Integer;
  Lock: TCriticalSection;
  I: Integer;
begin
  Lock := TCriticalSection.Create;
  try
    TParallel.For(1, 1000,
      procedure(I: Integer)
      begin
        Lock.Enter;
        try
          Total := Total + ComputeExpensive(I);
        finally
          Lock.Leave;
        end;
      end);
  finally
    Lock.Free;
  end;
end;

同期プリミティブ

TCriticalSection:相互排他(一度に1スレッドのみ)。TEvent:スレッド間シグナル(SetEvent/WaitFor)。マニュアルリセットのTEventはResetされるまでシグナル状態を維持。TInterlocked.Incrementはアトミックでシンプルなカウンタにクリティカルセクションより高速。TMonitor(TObjectに組み込み)も別のオプション。

delphi
uses
  System.SyncObjs;

var
  Lock: TCriticalSection;
  Event: TEvent;
  Count: Integer;
begin
  Lock := TCriticalSection.Create;
  Event := TEvent.Create(nil, True, False, '');
  try
    TThread.CreateAnonymousThread(
      procedure
      begin
        Lock.Enter;
        try
          Inc(Count);
        finally
          Lock.Leave;
        end;
        Event.SetEvent;  // signal completion
      end).Start;

    Event.WaitFor(INFINITE);  // wait for signal
  finally
    Lock.Free;
    Event.Free;
  end;
end;

TThread.QueueとSynchronize

UIコントロールはメインスレッドからのみ触れる。Synchronizeはメインスレッドが匿名メソッドを実行するまでワーカーをブロック — 控えめに使用(直列化を引き起こす)。Queueはポストして継続 — ファイアアンドフォーゲットUI更新に推奨。現在のスレッドを使用するにはthread引数にnilを渡す。

delphi
// From a worker thread, update UI safely:

// Synchronous — blocks worker until main thread runs the code
TThread.Synchronize(nil,
  procedure
  begin
    Label1.Caption := 'Done';
  end);

// Asynchronous — posts to main thread queue, doesn't block
TThread.Queue(nil,
  procedure
  begin
    Label1.Caption := 'Progress: 50%';
  end);

// TThread.Queue is preferred for non-critical updates
// Synchronize for cases where you need the result before continuing
28

パッケージとコンポーネント

パッケージプロジェクトの基本

パッケージ(.bpl)はDelphiメタデータ付きDLL — アプリ間でコードを共有。'requires'が依存関係をリスト。'contains'がこのパッケージ内のユニットをリスト。デザインタイムパッケージはコンポーネントをIDEにインストール、ランタイムパッケージはアプリに同梱。IDEの肥大化を防ぐためデザイン/ランタイムを分割。

delphi
// MyPackage.dpk
package MyPackage;

{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$DESIGNONLY MyDesignUnits}  // design-time only
{$RUNONLY MyRuntimeUnits}    // runtime only

requires
  rtl,
  vcl,
  System.Generics.Collections;

contains
  MyUnit1 in 'MyUnit1.pas',
  MyUnit2 in 'MyUnit2.pas',
  MyComponent in 'MyComponent.pas';

end.

// Build configurations:
//   - Build (debug)
//   - Release
//   - Design-time (installs into IDE)
//   - Runtime (deployed with app)

カスタムコンポーネントの骨組み

最も近い既存クラスから派生(TCustomLabelは公開プロパティなしのラベルを提供)。公開したいプロパティのみ再公開。RegisterプロシージャがコンポーネントをIDEパレットに追加。'default'が初期値を設定(コンストラクタと一致必要)。Registerはデザインタイムパッケージに配置。

delphi
unit MyLabel;

interface

uses
  Vcl.Controls, Vcl.Graphics, Vcl.StdCtrls;

type
  TMyLabel = class(TCustomLabel)
  private
    FHighlightColor: TColor;
    procedure SetHighlightColor(Value: TColor);
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property HighlightColor: TColor
      read FHighlightColor write SetHighlightColor default clYellow;
    property Caption;
    property Font;
    property OnClick;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('MyPalette', [TMyLabel]);
end;

コンポーネントプロパティとエディタ

TComponentが非ビジュアルコンポーネントの基底。サブオブジェクト(FItems)を所有 — コンストラクタで作成、デストラクタで解放。TStringsプロパティは組み込み文字列エディタを取得。RegisterPropertyEditorが特定のプロパティのオブジェクトインスペクタをカスタマイズ。ストリーミングが必要なネストしたオブジェクトにはTPersistentを使用。

delphi
type
  TMyComponent = class(TComponent)
  private
    FItems: TStringList;
    function GetItems: TStrings;
    procedure SetItems(Value: TStrings);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Items: TStrings read GetItems write SetItems;
  end;

constructor TMyComponent.Create(AOwner: TComponent);
begin
  inherited;
  FItems := TStringList.Create;
end;

destructor TMyComponent.Destroy;
begin
  FItems.Free;
  inherited;
end;

// Property editor for rich editing in Object Inspector:
//   RegisterPropertyEditor(TypeInfo(TStrings), TMyComponent, 'Items',
//     TStringListProperty);

イベントとメソッドポインタ

イベント型は'of object'付きプロシージャ型 — オブジェクト参照とメソッドポインタの両方を保持。呼ぶ前に常にAssigned()をチェック — nilイベントはAVをスロー。Do*メソッド(DoChange、DoClick)がイベントを発生させるprotectedディスパッチャ。サブクラスはDo*をオーバーライドしてイベントをインターセプト可能。

delphi
type
  TMyEvent = procedure(Sender: TObject; Value: Integer) of object;

  TMyComponent = class(TComponent)
  private
    FOnChange: TMyEvent;
  protected
    procedure DoChange(Value: Integer);
  published
    property OnChange: TMyEvent read FOnChange write FOnChange;
  end;

procedure TMyComponent.DoChange(Value: Integer);
begin
  if Assigned(FOnChange) then
    FOnChange(Self, Value);
end;

// 'of object' makes it a method pointer — must be assigned to a method
// (e.g., Form1.Button1Click). Always check Assigned before calling.

ストリーミングと永続化

TPersistentがストリーミングとAssignを可能に。公開プロパティは自動的にDFMファイルに保存。Assignをオーバーライドしてオブジェクト間のコピーをサポート。DefinePropertiesが非公開データをストリームに追加。WriteComponent/ReadComponentが任意のTStreamにシリアライズ。これがフォームが状態を永続化する仕組み。

delphi
type
  TMySettings = class(TPersistent)
  private
    FTimeout: Integer;
    FTitle: string;
  published
    property Timeout: Integer read FTimeout write FTimeout default 30;
    property Title: string read FTitle write FTitle;
  end;

// TPersistent enables streaming (DFM, RTTI):
//   - Inherits from TPersistent (gives Assign)
//   - Published properties are streamed
//   - Override AssignTo/Assign for custom copy

// Save to DFM automatically:
//   - Component owned by a form is streamed
//   - Sub-properties (TPersistent) are nested in DFM
//   - Use DefineProperties for non-standard data

// Manual streaming:
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create('settings.bin', fmCreate);
  try
    Stream.WriteComponent(MyComponent);
  finally
    Stream.Free;
  end;
end;

Was this helpful?