<6> Dephi のオブジェクト指向拡張 (Pascal へのオブジェクト指向拡張の歴史と Delphi)

# 6. Dephi のオブジェクト指向拡張
1995 年に発売された **Delphi** の言語は **Object Pascal** と呼ばれました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/1fa6c937-5ba4-788c-081a-0fb9ca3f3277.png)
Delphi のオブジェクト指向拡張を細かいトコまで説明していると大変なのでざっくりとした説明に留めます。

なお、サンプルコード中で `//` による行コメントを多用していますが、ご了承ください [^9]。

**See also:**

 - [Delphi (Wikipedia)](https://ja.wikipedia.org/wiki/Delphi)


## 6.1. Delphi のオブジェクト型
Delphi には [Turbo Pascal 由来のオブジェクト型](https://qiita.com/ht_deko/items/5935a06ca7aee27842f6)が下位互換性 [^4] のために残されています。非推奨でドキュメントも非常に少ないのですが、今でも使う事ができます。

```pascal:ObjTest.dpr
program ObjTest;
{$APPTYPE CONSOLE}

type
  TObj = object
  private
    FValue: Integer;
  public
    constructor Init;
    destructor Done; virtual;
    function Multiply(n: Integer): Integer;
    property Value: Integer read FValue write FValue;
  end;
  PObj = ^TObj;

  { TObj }
  constructor TObj.Init;
  begin
    FValue := 1;
  end; { TObj.Init }

  destructor TObj.Done;
  begin
    ;
  end; { TObj.Done }

  function TObj.Multiply(n: Integer): Integer;
  begin
    result := FValue * n
  end; { TObj.Multiply }

var
  sObj: TObj;
  dObj: PObj;
begin
  // Static
  sObj.Init;
  sObj.Value := 3;
  Writeln(sObj.Multiply(5));

  // Dynamic
  dObj := New(PObj, Init);
  try
    dObj^.Value := 7;
    Writeln(dObj^.Multiply(9));
  finally
    Dispose(dObj, Done);
  end;

  Readln;
end. { Main }
```

Delphi では、ポインタの逆参照を自動的に想定する参照モデルを使うため、動的なオブジェクトを指すための逆参照演算子 (`^`) を省略する事ができます。上記コードだと、メインブロック中の `^` を取り除いても動作します [^5]。定義部の `^TObj` の前にある `^` は逆参照演算子ではないので省略できません。

**See also:**

 - [オブジェクト型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%A8%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%EF%BC%88Delphi%EF%BC%89#.E3.82.AA.E3.83.96.E3.82.B8.E3.82.A7.E3.82.AF.E3.83.88.E5.9E.8B)

## 6.2. Delphi のクラス型
`クラス` という用語は Delphi 1 において、次のように定義されています。

> データと関連コードを表す機能を一つの単位に集めたリスト。クラスには定義にある機能だけではなく、上位オブジェクトから継承した機能も含まれます。「クラス」という用語は「オブジェクト型」と同義です。

Turbo Pascal では `クラス` という用語を使っていなかったので混乱しそうになりますが、Delphi ではオブジェクト型もクラス型も (広義の) クラス型です。クラス型はレコード型と同様、構造化型の一種です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/fbb9494f-d010-48d2-6604-a95e3a986885.png)
クラス型は型宣言部 (**type**) にて "型名 = **class** (親クラス) ～ **end**;" として定義します。親クラスは省略できますが、省略した場合にはルートクラスである **TObject** を継承します。

クラスには次に挙げるようなメンバーがあります。

### フィールド
[フィールド](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%EF%BC%88Delphi%EF%BC%89)はオブジェクトに属する変数のようなものです。他言語での "メンバ変数" や "インスタンス変数" に相当します。通常は可視性を **private** で定義します。

なお、クラスのフィールドを "クラスフィールド" とか "クラス変数" と呼んではいけません。そちらには別の意味があります。

### メソッド
[メソッド](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%EF%BC%88Delphi%EF%BC%89)とはクラス型宣言内で宣言された、オブジェクトに対して操作を行う手続き (**procedure**) または関数 (**function**) を指します。他言語の "メンバ関数" に相当します。

コンストラクタとデストラクタはオブジェクトの作成と廃棄を制御する特殊なメソッドです。

なお、クラスのメソッドを "クラスメソッド" とか "クラス関数" と呼んではいけません。そちらには別の意味があります。

### プロパティ
[プロパティ](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3)はオブジェクトの属性を定義するものです。フィールドの値を読み書きするメソッド (ゲッター/セッター) をカプセル化したりできます。プロパティの例には、フォームのキャプション、フォントのサイズ、データベーステーブルの名前などがあります。

**See also:**

 - [クラス型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%A8%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%EF%BC%88Delphi%EF%BC%89#.E3.82.AF.E3.83.A9.E3.82.B9.E5.9E.8B)
 - [構造化型（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89)

## 6.2.1. クラス型の定義
次のようにしてクラスを定義できます。

```pascal
type
  TCls = class(TObject) { TObject を継承したクラス型 TCls の定義 }
  private
    FValue: Integer;
  end;
```

 - ルートクラスは TObject です。
 - 親クラスを指定しなかった場合の親クラスは TObject です。
 - 多重継承はできません。
 - 可視性指定子があります。
 - プロパティがあります。
 - クラスメソッドがあります。
 - 静的なオブジェクトは作れません。

## 6.2.2. クラス型の定義
クラスの作成と破棄は次のようにして行います。次のコードは `ObjTest.dpr` のクラス型版です。

```pascal:ClsTest.dpr
program ClsTest;
{$APPTYPE CONSOLE}

type
  TCls = class
  private
    FValue: Integer;
  public
    constructor Create;
    destructor Destroy; override;
    function Multiply(n: Integer): Integer;
    property Value: Integer read FValue write FValue;
  end;

  { TCls }
  constructor TCls.Create;
  begin
    FValue := 1;
  end; { TCls.Create }

  destructor TCls.Destroy;
  begin
    inherited;
  end; { TCls.Destroy }

  function TCls.Multiply(n: Integer): Integer;
  begin
    result := FValue * n
  end; { TCls.Multiply }

var
  Cls: TCls;
begin
  Cls := TCls.Create;
  try
    Cls.Value := 7;
    Writeln(Cls.Multiply(9));
  finally
    Cls.Free;
  end;

  Readln;
end. { Main }
```

コンストラクタ Create() と Free() メソッドですね。自動参照カウント (ARC) [^10] が有効な場合には DisposeOf() メソッドを使って破棄します。

コンストラクタには結果型の定義はありませんが、コンストラクタが生成したオブジェクトまたはコンストラクタを呼び出したオブジェクトへの参照が返されます。**with** 文を使って、変数の宣言なしにクラスを使う事もできます。

```pascal
  with TStringList.Create do
    begin
      Add('Hello,');
      Add('world.');
      Writeln(Text);
      Free;
    end;
```

クラスではクラス型とポインタ型を別々に宣言したり、オブジェクトポインタを明示的に逆参照する必要はありません。

**See also:**

 - [System.TObject.Free (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.TObject.Free)
 - [System.TObject.DisposeOf (DocWiki: 10.3 Rio)](http://docwiki.embarcadero.com/Libraries/Rio/ja/System.TObject.DisposeOf)
 - [Delphi モバイル コンパイラでの自動参照カウント (DocWiki: 10.3 Rio)](http://docwiki.embarcadero.com/RADStudio/Rio/ja/Delphi_%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB_%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%81%A7%E3%81%AE%E8%87%AA%E5%8B%95%E5%8F%82%E7%85%A7%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88)

### 6.2.3. 可視性指定子 (Visibility Specifiers)
Delphi の可視性指定子とその範囲は次の通りです。

|  | 同一ユニット | 別ユニット | 下位クラス | 実行時<br>型情報 | オートメーション<br>型情報|
|:--|:-:|:-:|:-:|:-:|:-:|
| **strict private** [^7]  | × | × | × | × | × |
| **private**         | 〇| × | × | × | × |
| **strict protected** [^7]| × | × | 〇| × | × |
| **protected**       | 〇| × | 〇| × | × |
| **public**          | 〇| 〇| 〇| × | × |
| **published**       | 〇| 〇| 〇| 〇| × |
| **automated** (Win32)| 〇| 〇| 〇| × | 〇|

**published** を使うには `{$M+}` コンパイラ指令を付ける必要があります。また、**published** に指定できるフィールドはクラス型またはインターフェイス型に限定されます。

```pascal:Unit1.pas
unit Unit1;

interface

type
  TValue = class
  private
    FValue: Integer;
  public
    property Value: Integer read FValue write FValue;
  end;

  TClsA = class
    procedure Dummy; virtual;
  strict private
    FStrictPrivate: TValue;
  private
    FPrivate: TValue;
  strict protected
    FStrictProtected: TValue;
  protected
    FProtected: TValue;
  public
    FPublic: TValue;
  published
    FPublished: TValue;
  end;

  TClsB = class(TClsA)
    procedure Dummy; override;
  end;

  procedure DummyU1;

implementation

procedure DummyU1;
var
  V: Integer;
  ClsA: TClsA;
  ClsB: TClsB;
begin
  // --------------------------------------
  // Unit1.TClsA
  // Unit1.ClsA <- アクセス
  // --------------------------------------
  ClsA := TClsA.Create;
  try
    try
//    V := ClsA.FStrictPrivate.Value;   // NG
      V := ClsA.FPrivate.Value;
//    V := ClsA.FStrictProtected.Value; // NG
      V := ClsA.FProtected.Value;
      V := ClsA.FPublic.Value;
      V := ClsA.FPublished.Value;
    except
    end;  
  finally
    ClsA.Free;
  end;

  // --------------------------------------
  // Unit1.TClsA, Unit1.TClsB(継承)
  // Unit1.ClsB <- アクセス
  // --------------------------------------
  ClsB := TClsB.Create;
  try
    try
  //  V := ClsB.FStrictPrivate.Value;   // NG
      V := ClsB.FPrivate.Value;
  //  V := ClsB.FStrictProtected.Value; // NG
      V := ClsB.FProtected.Value;
      V := ClsB.FPublic.Value;
      V := ClsB.FPublished.Value;
    except
    end;  
  finally
    ClsB.Free;
  end;
end; { Dummy }

{ TClsA }

procedure TClsA.Dummy;
var
  V: Integer;
begin
// --------------------------------------
// Unit1.TClsA
// Unit1.TClsA.Self <- アクセス
// --------------------------------------
  V := Self.FStrictPrivate.Value;
  V := Self.FPrivate.Value;
  V := Self.FStrictProtected.Value;
  V := Self.FProtected.Value;
  V := Self.FPublic.Value;
  V := Self.FPublished.Value;
end;

{ TClsB }

procedure TClsB.Dummy;
var
  V: Integer;
begin
// --------------------------------------
// Unit1.TClsA, Unit1.TClsB(継承)
// Unit1.TClsB.Self <- アクセス
// --------------------------------------
//V := Self.FStrictPrivate.Value;
  V := Self.FPrivate.Value;
  V := Self.FStrictProtected.Value;
  V := Self.FProtected.Value;
  V := Self.FPublic.Value;
  V := Self.FPublished.Value;
end;

end.
```

```pascal:Unit2.pas
unit Unit2;

interface

uses
  Unit1;

type
  TClsC = class(TClsA)
    procedure Dummy; override;
  end;

  procedure DummyU2;

implementation

procedure DummyU2;
var
  V: Integer;
  ClsA: TClsA;
  ClsB: TClsB;
  ClsC: TClsC;
begin
  // --------------------------------------
  // Unit1.TClsA
  // Unit2.ClsA <- アクセス
  // --------------------------------------
  ClsA := TClsA.Create;
  try
    try
//    V := ClsA.FStrictPrivate.Value;   // NG
//    V := ClsA.FPrivate.Value;         // NG
//    V := ClsA.FStrictProtected.Value; // NG
//    V := ClsA.FProtected.Value;       // NG
      V := ClsA.FPublic.Value;
      V := ClsA.FPublished.Value;
    except
    end;
  finally
    ClsA.Free;
  end;

  // --------------------------------------
  // Unit1.TClsA, Unit1.TClsB
  // Unit2.ClsB <- アクセス
  // --------------------------------------
  ClsB := TClsB.Create;
  try
    try
//    V := ClsB.FStrictPrivate.Value;   // NG
//    V := ClsB.FPrivate.Value;         // NG
//    V := ClsB.FStrictProtected.Value; // NG
//    V := ClsB.FProtected.Value;       // NG
      V := ClsB.FPublic.Value;
      V := ClsB.FPublished.Value;
    except
    end;
  finally
    ClsB.Free;
  end;

  // --------------------------------------
  // Unit1.TClsA, Unit2.TClsC
  // Unit2.ClsC <- アクセス
  // --------------------------------------
  ClsC := TClsC.Create;
  try
    try
//    V := ClsC.FStrictPrivate.Value;   // NG
//    V := ClsC.FPrivate.Value;         // NG
//    V := ClsC.FStrictProtected.Value; // NG
      V := ClsC.FProtected.Value;
      V := ClsC.FPublic.Value;
      V := ClsC.FPublished.Value;
    except
    end;
  finally
    ClsC.Free;
  end;

end; { DummyU2 }

{ TClsC }
procedure TClsC.Dummy;
var
  V: Integer;
begin
// --------------------------------------
// Unit1.TClsA, Unit2.TClsC(継承)
// Unit2.TClsC.Self <- アクセス
// --------------------------------------
//V := Self.FStrictPrivate.Value;   // NG
//V := Self.FPrivate.Value;         // NG
  V := Self.FStrictProtected.Value;
  V := Self.FProtected.Value;
  V := Self.FPublic.Value;
  V := Self.FPublished.Value;
end;

end.
```

## 6.3. Delphi のクラス参照型 (メタクラス)
クラスのインスタンス (オブジェクト) へのポインタはクラス型の変数です。**クラス参照型 (メタクラス)** [^6] はクラス型そのものに対するポインタです。

クラス参照型は型宣言部 (**type**) にて "型名 = **class of** 対象クラス型;" として定義します。次のようにしてクラス参照型を定義できます。

```pascal:
type
  TObjectClass = class of TObject;
```

考え方はポインタ型と同じです。

```pascal
type
  TMyClass = class
    function FuncA: Integer;
    class function FuncB: Integer;
  end;
  MyC = class of TMyClass; // TMyClass のクラス参照型

  function TMyClass.FuncA: Integer;
  begin
    ...
  end;

  class function TMyClass.FuncB: Integer;
  begin
    ...
  end;

var
  MyClass: TMyClass;
  v: Integer;
begin
  MyClass := TMyClass.Create; // <- クラス参照でコンストラクタが呼び出されている
  v := MyClass.FuncA;         // オブジェクト参照
  v := TMyClass.FuncB;        // クラス参照
  v := MyC.FuncB;             // クラス参照型を使ったクラス参照
  ...
```

MyClass は TMyClass のオブジェクトです。正確には TMyClass のインスタンスへのポインタです。 FuncA はオブジェクト参照で呼び出されています。コード中の FuncB は TMyClass のクラスメソッドなのでクラス参照で呼び出されています。

クラスメソッドはクラス参照だけでなく、オブジェクト参照でも呼び出す事ができます。

コンストラクタは通常、クラス参照で呼び出されます。コンストラクタをオブジェクト参照で呼び出した場合、オブジェクト (インスタンス) は生成されず、通常のメソッドとして実行されます。

**See also:**

 - [クラス参照型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AF%E3%83%A9%E3%82%B9%E5%8F%82%E7%85%A7#.E3.82.AF.E3.83.A9.E3.82.B9.E5.8F.82.E7.85.A7.E5.9E.8B)


## 6.4. Delphi のインターフェイス型
(オブジェクト)インターフェイスはクラスに実装させることができるメソッドを定義します。インターフェイスはクラスと同じように宣言しますが、直接インスタンス化することはできません。

インターフェイス型は Delphi 3 で実装され、Delphi 6 で汎用的な IInterface (以前は IUnknown) が使えるようになりました。また、インターフェイスを使うと多重継承に似た事ができます。インターフェイス型もまた構造化型です。

**See also:**

 - [インターフェイス型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88_%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%82%A4%E3%82%B9#.E3.82.A4.E3.83.B3.E3.82.BF.E3.83.BC.E3.83.95.E3.82.A7.E3.82.A4.E3.82.B9.E5.9E.8B)
 - [構造化型（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89)
 - [COM (Wikipedia)](https://ja.wikipedia.org/wiki/Component_Object_Model)


### 6.4.1. インターフェイス型の定義
インターフェイス型は型宣言部 (**type**) にて "型名 = **interface** (親インターフェイス) ['GUID'] ～ **end**;" として定義します。

次のようにしてインターフェイス型を定義できます。GUID は Delphi のコードエディタで〔Ctrl〕+〔Shift〕+〔G〕で生成できます。インターフェイス型の親インターフェイスを省略するとルートインターフェイス **IInterface** から派生します。

```pascal
type
  IItfA = interface
  ['{xxxxxxxx-6953-4C6B-B1E3-DBB2E7419162}']
    function FuncA: string;
  end;

  IItfB = interface(IItfA)
  ['{xxxxxxxx-261D-4742-BC7E-8D05B22D8016}']
    function FuncB: string;
  end;
```

### 6.4.2. インターフェイス型を使ったクラス
インターフェイスを使ったクラスは基本的に **TInterfacedObject** (またはその下位クラス) から派生します。IItfB を使ったクラスの実装例を次に示します。

```pascal
type
  TCls = class (TInterfacedObject, IItfB)
    function FuncA: string;
    function FuncB: string;
  end;

  function TCls.FuncA: string;
  begin
    result := 'FuncA';
  end;

  function TCls.FuncB: string;
  begin
    result := 'FuncB';
  end;
```

このクラスを使ってみます。

```pascal
var
  Cls: TCls;
begin
  Cls := TCls.Create;
  try
    Writeln(Cls.FuncA);
    Writeln(Cls.FuncB);
  finally
    Cls.Free;
  end;
  Readln;
end.
```

実行結果は次の通りです。

```
FuncA
FuncB
```

クラス型変数ではなくインターフェイス型変数を使うと参照カウントが行われるようになり、Free での破棄が不要になります。

```pascal
var
  Cls: IItfB; // <- インターフェイス型変数を使う
begin
  ReportMemoryReakOnShutdown := True; // アプリケーション終了時にメモリリークを報告する
  Cls := TCls.Create;
  Writeln(Cls.FuncA);
  Writeln(Cls.FuncB);
  Readln;
end.
```

**See also:**

 - [インターフェイス参照 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%82%A4%E3%82%B9%E5%8F%82%E7%85%A7)
 - [Delphi の Interface (.delphi maniacs)](http://delphimaniacs.blogspot.com/2013/12/delphi-interface.html)
 - [インターフェース使っててハマりそうなところをつらつらと (Qiita:ktz_alias)](https://qiita.com/ktz_alias/items/d9d7dcc60c76b79d71e3)
 - [Delphiでインターフェースを使う (偏った言語信者の垂れ流し)](https://tokibito.hatenablog.com/entry/20151117/1447689541)

## 6.5. Delphi の抽象クラス型
Delphi 2005 以降では次のようにして抽象クラス型を定義できます。

```pascal
type
  TCls = class abstract (TObject) { TObject を継承した抽象クラス型 TCls の定義 }
  end;
```

...なのですが、何故か抽象クラス型をインスタンス化できてしまい、エラーも警告も出ません。つまり **abstract** の指定はマーカーとしての意味しかありません。

**abstract** を指定していなくても抽象メソッドが含まれていれば抽象クラスになります。こちらもインスタンス化できてしまうのですが、コンパイラが警告 W1020 を吐きます。

次のようなコンパイラ指令を書いておけば警告をエラーに昇格できるため、抽象メソッドを含むクラスのインスタンス化は阻止する事ができます。

```pascal
{$WARN CONSTRUCTING_ABSTRACT ERROR}
```

なお、**abstract** の代わりに **sealed** を指定すると継承できないクラス (シールドクラス/ファイナルクラス) を作る事ができます。こちらの方は継承しようとするとちゃんとエラーになります...謎です。


## 6.6. Delphi の高度なレコード型 (Advanced Record)
Delphi 2006 からレコード型にメソッドやプロパティ、コンストラクタを持たせられるようになりました。次のコードは `ObjTest.dpr` の高度なレコード型版です。

```pascal:RecTest.dpr
program RecTest;
{$APPTYPE CONSOLE}

type
  TRec = record
  private
    FValue: Integer;
  public
    constructor Create(InitialValue: Integer);
    procedure Destroy;
    function Multiply(n: Integer): Integer;
    property Value: Integer read FValue write FValue;
  end;
  PRec = ^TRec;

  { TRec }
  constructor TRec.Create(InitialValue: Integer);
  begin
    FValue := InitialValue;
  end; { TRec.Init }

  procedure TRec.Destroy;
  begin
    ;
  end; { TRec.Destroy }

  function TRec.Multiply(n: Integer): Integer;
  begin
    result := FValue * n
  end; { TRec.Multiply }

var
  sRec: TRec;
  dRec: PRec;
begin
  // Static (1)
  sRec.Value := 3;
  Writeln(sRec.Multiply(5));

  // Static (2)
  sRec := sRec.Create(3);
  Writeln(sRec.Multiply(5));

  // Static (3)
  sRec.Create(3);
  Writeln(sRec.Multiply(5));

  // Dynamic
  dRec := New(PRec, Create(0));
  try
    dRec^.Value := 7;
    Writeln(dRec^.Multiply(9));
    dRec^.Destroy;
  finally
    Dispose(dRec);
  end;

  Readln;
end. { Main }
```

オブジェクト型とほぼ同じような使い方ができるようになっています。メインブロック中の `^` を取り除いても動作します [^5]。Destroy() がデストラクタではなくただのメソッドになっている事に注意してください。

 - コンストラクタには最低でも一つのパラメータが必要で、初期値を持つパラメータは初期値が指定されない可能性がある (内部で使っている引数なしのコンストラクタとカブる) ため、使用する事ができません。
 - デストラクタはありません。
 - レコードなので継承はできません。
 - レコードなので可変部は普通に使えます。

サンプルコードの `(2)` の所は、クラスのように書いてはいけないのでしょうか？

```pascal
  // Static (2)
  sRec := TRec.Create(3);
  Writeln(sRec.Multiply(5));
```

書けるのは書けるのですが、この処理は、

 1. TRec 型の (静的な) 変数 sRec が作られる。
 2. 無名の TRec 型動的変数が作られる。
 3. sRec に動的変数が**コピーされる**。

となってしまいます (一時的に TRec 型の変数が 2 つ存在する)　のでオススメしません。

**See also:**

 - [高度なレコード型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E3.83.AC.E3.82.B3.E3.83.BC.E3.83.89.E5.9E.8B.EF.BC.88.E9.AB.98.E5.BA.A6.EF.BC.89)


## 6.7. カスタム管理レコード型 (Custom Managed Records)
Delphi 10.4 Sydney からコンストラクタやデストラクタのような働きをする演算子によって、初期化時と終了時の処理を行う事ができるようになりました。例を挙げます。

```pascal:CustomManagegRecordTest.dproj
program CustomManagegRecordTest;

{$APPTYPE CONSOLE}

type
  TMyRecord = record
    Value: Integer;
    class operator Initialize(out Dest: TMyRecord);
    class operator Finalize(var Dest: TMyRecord);
  end;

{ TMyRecord }

class operator TMyRecord.Initialize(out Dest: TMyRecord);
begin
  Dest.Value := 10;
  Writeln('Start!');
end;

class operator TMyRecord.Finalize(var Dest: TMyRecord);
begin
  Writeln('Done.');
end;

begin
   begin
     var Rec: TMyRecord;
     Writeln(Rec.Value);
   end;
  Readln;
end.
```

カスタム管理レコード型 (TMyRecord) である Rec を**空ブロック**の中で使っています [^8]。これの実行結果は次のようになります。

```
Start!
10
Done.
```

コンストラクタやデストラクタ (ありませんけど) を明示的に呼び出さなくても、初期化コードと終了時コードが実行されます。

| 演算子 | 説明 |
|:---|:---:
| **Initialize**  | 初期化処理を行います |
| **Finalize** | 終了時処理を行います |



**See also:**

 - [カスタム管理レコード (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E7%AE%A1%E7%90%86%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89)
 - [Delphi 10.4の新機能: カスタム マネージド レコード (Embarcadero Blogs)](https://community.idera.com/developer-tools/b/blog/posts/custom-managed-records-coming-to-delphi-10-4-ja)
 - [Custom Managed Records Coming in Delphi 10.3 (Marco Tech Blog)](https://blog.marcocantu.com/blog/2018-november-custom-managed-records-delphi.html) - 10.3 で実装されるハズだった時の仕様
 - [[Delphi] カスタム管理レコードを使ったスマートポインタ (Qiita: @pik)](https://qiita.com/pik/items/6e8c3df6095581bf39ee)

### 6.7.1. 代入とコピー

カスタム管理レコード型は **継承できない (静的な) オブジェクト型のようなもの** という表現がしっくりくるでしょうか。

それでも、レコード型には変わりないので、代入はコピーとなります。ポインタや参照の代入ではありません。

```pascal:CustomManagegRecordTest.dproj

...

begin
  begin
    var Rec1: TMyRecord;
    var Rec2: TMyRecord;

    Rec1.Value := 11;
    Rec2 := Rec1;
    Rec1.Value := 10;

    Writeln(Rec1.Value);
    Writeln(Rec2.Value);
  end;
  Readln;
end.
```

結果は次のようになります。

```
Start!
Start!
10
11
Done.
Done.
```

初期化処理と終了時処理が 2 つ分行われています。

### 6.7.2. Assign 演算子

Assign 演算子を使うと、代入時の挙動もカスタマイズできます。

| 演算子 | 説明 |
|:---|:---:
| **Assign** | 代入時の処理を行います |


次に例を挙げます。

```pascal:CustomManagegRecordTest.dproj
program CustomManagegRecordTest;

{$APPTYPE CONSOLE}

type
  TMyRecord = record
    Value: Integer;
    class operator Initialize(out Dest: TMyRecord);
    class operator Finalize(var Dest: TMyRecord);
    class operator Assign (var Dest: TMyRecord; const [ref] Src: TMyRecord);
  end;

{ TMyRecord }

class operator TMyRecord.Initialize(out Dest: TMyRecord);
begin
  Dest.Value := 10;
  Writeln('Start!');
end;

class operator TMyRecord.Finalize(var Dest: TMyRecord);
begin
  Writeln('Done.');
end;

class operator TMyRecord.Assign (var Dest: TMyRecord; const [ref] Src: TMyRecord);
begin
  Dest.Value := Src.Value * 2; // ニヴァイ
  Writeln('Multiply!');
end;

begin
  var Rec1: TMyRecord;
  var Rec2: TMyRecord;

  Rec1.Value := 11;
  Rec2 := Rec1;
  Rec1.Value := 10;

  Writeln(Rec1.Value);
  Writeln(Rec2.Value);

  Readln;
end.
```

実行結果は次のようになります。

```
Start!
Start!
Multiply!
10
22
Done.
Done.
```

### 6.7.3. コンストラクタと Intialize 演算子の併用

コンストラクタと **Initialize** 演算子を併用するとどうなるのでしょう？

```pascal:CustomManagegRecordTest.dproj
program CustomManagegRecordTest;

{$APPTYPE CONSOLE}

type
  TMyRecord = record
    Value: Integer;
    constructor Create(InitialValue: Integer);
    class operator Initialize(out Dest: TMyRecord);
    class operator Finalize(var Dest: TMyRecord);
  end;

{ TMyRecord }

constructor TMyRecord.Create(InitialValue: Integer);
begin
  Self.Value := InitialValue;
  Writeln('Begin!');
end;

class operator TMyRecord.Initialize(out Dest: TMyRecord);
begin
  Dest.Value := 10;
  Writeln('Start!');
end;

class operator TMyRecord.Finalize(var Dest: TMyRecord);
begin
  Writeln('Done.');
end;

begin
  with TMyRecord.Create(3) do
    Writeln(Value);
  Readln;
end.
```

結果はこうなりました。

```
Start!
Begin!
3
Done.
```

イニシャライザが先に走るのですね。

## 6.8. Delphi のヘルパー

Delphi にはヘルパーと呼ばれる機能があります。オブジェクトを継承せずに機能を拡張するもので、大別すると次の 3 種類があります。

 - クラスヘルパー
 - レコードヘルパー
 - レコードヘルパー \(組み込み型用\)

クラスヘルパーとレコードヘルパーは Delphi 2006 から利用可能でしたが、真価を発揮するのは Delphi 2009 以降です。XE4 以降では Integer 等のオブジェクトではない組み込み型に対してもヘルパーを作用させる事ができるようになっています。

**See also:**

 - [クラス ヘルパとレコード ヘルパ（Delphi） (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AF%E3%83%A9%E3%82%B9_%E3%83%98%E3%83%AB%E3%83%91%E3%81%A8%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89_%E3%83%98%E3%83%AB%E3%83%91%EF%BC%88Delphi%EF%BC%89)
 - [Delphi の型をレコードヘルパで拡張する (Qiita)](https://qiita.com/ht_deko/items/950f472efcdf1c0361b7)
 - [【Delphi】クラスヘルパによる private メンバアクセスの件を with 文で回避できると聞いて (Qiita)](https://qiita.com/ht_deko/items/0a6588819c5102646a79)

## 6.9. Delphi のレコード型、オブジェクト型、クラス型、インターフェイス型の主な違い

Delphi でのレコード型、オブジェクト型、クラス型の主な違いは次の通りです。

|  | レコード型 | レコード型<br>(高度) |　レコード型<br>(カスタム管理) | オブジェクト型 |クラス型 |インターフェイス型|
|:---|:---:|:---:|:---:|:---:|:---:|:---:|
| 定義 | **record**  | **record**  | **record**  | **object** | **class** | **interface** |
| 継承 | × | × | × | 〇 | 〇 | 〇 |
| フィールド | 〇 | 〇 | 〇 | 〇 | 〇 | × |
| 可変部 | 〇 | 〇 | 〇 | × | × | × |
| メソッド | × | 〇 | 〇 | 〇 | 〇 | 〇 |
| プロパティ | × | 〇 | 〇 | △ [^1] | 〇 | 〇 |
| 可視性指定子 | × | △ [^2] | △ [^2]| 〇 | 〇 | × |
| コンストラクタ | × | △ [^3] | △ [^11] | 〇 | 〇 | × |
| デストラクタ | × | × | △ [^12] | 〇 | 〇 | × |

## 6.10. クラスに関連する用語

言語によってクラスに関連する用語に違いがあります。同じものを指しているのに、日本語訳された時に用語がゆらいでいるものもあります。

| 言語 | ルートクラス | クラス | スーパークラス | サブクラス |メッセージ|
|:---|:---:|:---:|:---:|:---:|:---:|
| **Delphi**  | TObject | クラス<br>(Class) | 直接上位<br>(Immediate Ancestor) | 直接下位<br>(Immediate Descendant) | メソッド呼び出し<br>(Method Call) |
| **Turbo Pascal**  | Object | オブジェクト型<br>(Object Type) | 直系上位<br>(Immediate Ancestor) | 直系下位<br>(Immediate Descendant) | メソッド呼び出し<br>(Method Call) |
| **Object Pascal<br>(Apple)**  | TObject | オブジェクト型<br>(Object Type) | 直系のアンセスタ (先祖)<br>(Immediate Ancestor) | 直系のディセンダント (子孫)<br>(Immediate Descendant) | メソッド呼び出し<br>(Method Call) |
| **Clascal**        | TObject | クラス<br>(Class) | スーパークラス<br>(Superclass) | サブクラス<br>(Subclass) | メソッド呼び出し<br>(Method Call) |
| **Smalltalk**        | Object | クラス<br>(Class) | スーパークラス<br>(Superclass) | サブクラス<br>(Subclass) | メッセージ<br>(message) |

 - Object Pascal (Apple) と Turbo Pascal ではクラスの事をオブジェクト型と呼んでいます。
 - Delphi のオブジェクト型は Turbo Pascal のオブジェクト型です。
 - Delphi ではオブジェクト型とは別にクラス型があります。
 - Delphi には Smalltalk の Message に概念のよく似た `メッセージメソッド` があります。

**See also:**

 - [メッセージメソッド (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%EF%BC%88Delphi%EF%BC%89#.E3.83.A1.E3.83.83.E3.82.BB.E3.83.BC.E3.82.B8_.E3.83.A1.E3.82.BD.E3.83.83.E3.83.89)
 - [Delphi / Object Pascal のメッセージシステム (Qiita: @pik)](https://qiita.com/pik/items/cfa223b9314f6ed8cb34)


# 参考文献

## Object Pascal Handbook
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/8818efc3-7a95-f06c-51da-50650a804075.png)

| タイトル | 著者 | ISBN-10<br>(Amazon) |出版年|
|:---|:---|:---:|:---:|
| Object Pascal Handbook | マルコ・カントゥ (著)|  [1514349949](https://www.amazon.co.jp/dp/1514349949/)  | 2015/8/5 |
| Object Pascal Handbook | マルコ・カントゥ (著)<br>藤井 等 (訳) |  [487783401X](https://www.amazon.co.jp/dp/487783401X/)  | 2016/6/10 |
| Object Pascal Handbook <br>Delphi 10.4 Sydney Edition| マルコ・カントゥ (著)|  [B08XZ44KXH](https://www.amazon.co.jp/dp/B08XZ44KXH/)  | 2021/3/3 |
| Object Pascal Handbook <br>Delphi 11 Alexandria Edition | マルコ・カントゥ (著)<br>藤井 等 (訳) |  [4877835210](https://www.amazon.co.jp/dp/4877835210/)  | 2022/4/10 |

最近の Delphi の文法はこれと [DocWiki](http://docwiki.embarcadero.com/RADStudio/ja/Delphi_%E8%A8%80%E8%AA%9E%E3%82%AC%E3%82%A4%E3%83%89%EF%BC%9A%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9) があればいいかと。英語版でよければ PDF を無償 DL できます。

 - [Object Pascal Handbook 英語版 (Embarcadero)](http://forms.embarcadero.com/DownloadMarcoCantueBook)
 - [Object Pascal Handbook Delphi 10.4 Sydney Edition (Embarcadero)](https://lp.embarcadero.com/Object-Pascal-Handbook-2021)
 - [Object Pascal Handbook Delphi 11.0 Alexandria Edition (Embarcadero)](https://lp.embarcadero.com/ObjectPascalHandbookD11)
 - [OBJECT PASCAL HANDBOOK に掲載のソースコード (GitHub)](https://github.com/MarcoDelphiBooks/ObjectPascalHandbook)
 - [Object Pascal Handbook Delphi 10.4 Sydney Edition / 11.0 Alexandria Edition に掲載のソースコード (GitHub)](https://github.com/MarcoDelphiBooks/ObjectPascalHandbook104)
 - [DocWiki (Embarcadero)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8)

## DELPHI クイックリファレンス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/b8449d08-7b91-432f-92ce-a9a145020e7a.png)

| タイトル | 著者 | ISBN-10<br>(Amazon) |出版年|
|:---|:---|:---:|:---:|
| [Delphi in a Nutshell: A Desktop Quick Reference](https://www.oreilly.com/library/view/delphi-in-a/1565926595/) | レイ・リシュナー (著)|  [1565926595](https://www.amazon.co.jp/dp/1565926595/)  | 2000/3/26 |
| [DELPHI クイックリファレンス](https://www.oreilly.co.jp/books/4873110408/) | レイ・リシュナー (著)<br>光田 秀 (訳)<br>竹田 知生 (訳) |  [4873110408](https://www.amazon.co.jp/dp/4873110408/)  | 2001/5/24 |

Delphi 5 時点の書籍ですが、Delphi の基本的な部分...言語仕様や組み込みルーチンに絞って書かれたものなので、かなりの部分が最新版の Delphi でも通用すると思います。

逆に言えば VCL 等のライブラリには一切触れられていないという事ですから、その点には注意が必要です。

 - [DELPHI クイックリファレンスに掲載のソースコード (ftp.oreilly.com)](ftp://ftp.oreilly.com/pub/examples/nutshell/delphi)
 - [Delphi in a Nutshell に掲載のソースコード (resources.oreilly.com)](https://resources.oreilly.com/examples/9781565926592/)
 - [Delphi in a Nutshell 正誤表 (oreilly.com)](https://www.oreilly.com/catalog/errata.csp?isbn=9781565926592)

## Object Pascal Language Guide

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/5c0ab61d-69b8-4c87-647c-435499d48c75.png)
Delphi 1.0 で加えられた変更を知る方法ですが、Delphi 2～4 の CD-ROM を持っているのなら `DELPHI16\EXTRAS\MANUALS\` にある `"Object Pascal Language Guide (OBJLANG.PDF)"` を参照してみてください。紙マニュアルやヘルプ (WinHelp) に書かれている事と同じですが、視認性はいいと思います。


[ [← 5. (標準) Pascal へのオブジェクト指向拡張](https://qiita.com/ht_deko/items/e23d8c6ae24f0a6c7500) ] [ [↑ 目次へ](https://qiita.com/ht_deko/items/ff2d90c9174290583377) ]

[^1]: Delphi 2 以降でプロパティが使えます。
[^2]: Publihed はありません。
[^3]: 1 つ以上のパラメータを持つ必要があります。
[^4]: 「下位互換性のために残してある」と言いながらも Delphi, Delphi 2 で拡張が行われており、例えば下にある `ObjTest.dpr` はプロパティが使われているために Delphi 1 ではコンパイルできません。
[^5]: Delphi 2 以降で(動的)オブジェクト型や(動的)レコード型の逆参照演算子が省略できます。
[^6]: Delphi 1 では `オブジェクト参照型` と呼ばれていました。とても紛らわしいネーミングです。
[^7]: Delphi 2005 以降で利用可能です。
[^8]: 空ブロックを使わないと、インライン宣言した変数の寿命は Readln() の後になります。
[^9]: Delphi 1 では行コメントが使えません。行コメントが使えるのは Delphi 2 以降です。
[^10]: ARC は Delphi 10.4 Sydney で廃止されました。
[^11]: コンストラクタと似たような働きをする Initialize 演算子があります。
[^12]: デストラクタと似たような働きをする Finalize 演算子があります。