メソッド(Delphi)
クラスとオブジェクト:インデックス への移動
目次
メソッドは、クラスに関連付けられている手続きまたは関数です。メソッドの呼び出しでは、そのメソッドの操作対象となるオブジェクト(クラス メソッドの場合はクラス)を指定します。たとえば、SomeObject.Free では、SomeObject の Free メソッドを呼び出しています。
このトピックで扱う内容は以下のとおりです。
- メソッドの宣言と実装
- メソッドのバインディング
- メソッドのオーバーロード
- コンストラクタとデストラクタ
- メッセージ メソッド
メソッドの概要
クラス宣言の中では、メソッドは手続きや関数のヘッダーとして記述され、それらは forward 宣言のような働きをします。各メソッドは、クラス宣言より後のどこか(ただし、同じモジュール内)で定義宣言によって実装する必要があります。たとえば、次のように、TMyClass の宣言に DoSomething というメソッドが含まれているとしましょう。
type
TMyClass = class(TObject)
...
procedure DoSomething;
...
end;
DoSomething の定義宣言は、この後このモジュール内で次のように記述されていなければなりません。
procedure TMyClass.DoSomething;
begin
...
end;
クラスの宣言はユニットの interface セクションでも implementation セクションでも行えますが、クラス メソッドの定義宣言は implementation セクションに記述されていなければなりません。
定義宣言のヘッダー部では、メソッド名は所属するクラスの名前で必ず限定されます。ヘッダー部には、クラス宣言で指定したパラメータ リストを再度指定してもかまいませんが、その場合は、パラメータの順序、型、名前がクラス宣言で指定されているものと完全に一致する必要があります。また、メソッドが関数の場合は、戻り値の型も一致する必要があります。
メソッド宣言には、他の関数や手続きでは使用されない特別な指令を付けることができます。これらの指令は定義宣言ではなくクラス宣言にのみ記載し、常に以下の順序で指定しなければなりません。
reintroduce; overload; <バインディング>; <呼び出し規約>; abstract; <警告>
ここで、
- <バインディング> は static、virtual、dynamic、override のいずれかです。
- <呼び出し規約> は、register、pascal、cdecl、stdcall、または safecall です。
- <警告> は、platform、deprecated、または library です。 これらの警告(ヒント)指令の詳細については、「<警告>」を参照してください。
すべての Delphi 指令は、「指令」に一覧されています。
Inherited
予約語 inherited は、多態的(ポリモーフィック)な動作を実装する上で特別な役割を果たします。これはメソッド定義で使用することができ、その後に識別子を伴う場合も伴わない場合もあります。
inherited の後にメンバの名前がある場合は、通常のメソッド呼び出しやプロパティまたはフィールドの参照を表します。ただし、参照先メンバの検索は現在問題にしているメソッドのクラスの直接の祖先(継承関係における上位概念)から始まります。たとえば、以下の行がメソッドの定義内にあると、
inherited Create(...);
継承された Create が呼び出されます。
inherited の後に識別子がない場合は、現在のメソッドと同じ名前の継承メソッドを指します。あるいは、現在のメソッドがメッセージ ハンドラの場合は、同じメッセージの継承メッセージ ハンドラを指します。この場合、inherited には明示的なパラメータは指定されませんが、現在のメソッドが呼び出されたときと同じパラメータが継承メソッドに渡されます。以下に例を示します。
inherited;
これは、コンストラクタの実装でよく登場します。現在のコンストラクタに渡されたのと同じパラメータで、継承されたコンストラクタが呼び出されます。
Self
メソッドの implementation セクションでは、そのメソッドが呼び出されたときのオブジェクトを識別子 Self で参照できます。 例として、Classes ユニットで定義されている TCollection の Add メソッドの実装を以下に示します。
function TCollection.Add: TCollectionItem;
begin
Result := FItemClass.Create(Self);
end;
この Add メソッドでは、FItemClass フィールドによって参照されるクラス(常に TCollectionItem の下位クラス)の Create メソッドを呼び出しています。TCollectionItem.Create は TCollection 型のパラメータを 1 つだけ取るため、Add では、TCollectionItem.Create に Add の呼び出し対象となる TCollection インスタンス オブジェクトを渡しています。これを以下のコードで説明します。
var MyCollection: TCollection;
...
MyCollection.Add // MyCollection is passed to the
// TCollectionItem.Create method
Self はさまざまな理由で役に立ちます。たとえば、あるクラス型で宣言されているメンバ識別子が、そのクラスのあるメソッドのブロックで再宣言されているとします。このような場合、Self.Identifier と指定することで元のメンバ識別子にアクセスできます。
クラス メソッドにおける Self の情報については、「クラス演算子」(クラス参照」内)を参照してください。
メソッドのバインディング
メソッドのバインディングは、静的(デフォルト)、仮想、動的のいずれかとすることができます。仮想メソッドと動的メソッドはオーバーライドすることができ、また、抽象メソッドにすることもできます。これらの指定は、あるクラス型の変数に下位クラス型の値が格納されているときに効果を発揮します。これらの指定により、メソッドが呼び出されたときにどの実装が起動されるかが決まるのです。
静的メソッド
メソッドは、デフォルトでは静的メソッドになります。静的メソッドが呼び出されたとき、どの実装が起動されるかは、そのメソッド呼び出しで使用されているクラス変数またはオブジェクト変数の宣言時(コンパイル時)の型によって決まります。次の例で、Draw メソッドは静的メソッドです。
type
TFigure = class
procedure Draw;
end;
TRectangle = class(TFigure)
procedure Draw;
end;
このように宣言されていることを前提として、静的メソッドを呼び出したときの効果を以下のコードを使って説明します。Figure.Draw の 2 番目の呼び出しでは、Figure 変数は TRectangle クラスのオブジェクトを参照していますが、Figure 変数の型が TFigure と宣言されているため、TFigure に定義されている Draw の実装が呼び出されます。
var
Figure: TFigure;
Rectangle: TRectangle;
begin
Figure := TFigure.Create;
Figure.Draw; // calls TFigure.Draw
Figure.Destroy;
Figure := TRectangle.Create;
Figure.Draw; // calls TFigure.Draw
TRectangle(Figure).Draw; // calls TRectangle.Draw
Figure.Destroy;
Rectangle := TRectangle.Create;
Rectangle.Draw; // calls TRectangle.Draw
Rectangle.Destroy;
end;
仮想メソッドと動的メソッド
メソッドを仮想(virtual)メソッドまたは動的(dynamic)メソッドにするには、メソッド宣言にそれぞれ virtual 指令または dynamic 指令を付け加えます。静的メソッドとは異なり、仮想メソッドと動的メソッドは下位クラスでオーバーライドすることができます。オーバーライドされたメソッドが呼び出されたとき、どの実装が起動されるかは、そのメソッド呼び出しで使用されているクラス変数またはオブジェクト変数の宣言時の型ではなく、実際の(実行時の)型によって決まります。
メソッドをオーバーライドするには、override 指令を使ってそのメソッドを再宣言します。override 宣言するときは、宣言されるパラメータの順序と型が上位クラスでの宣言と一致する必要があり、戻り値がある場合は、その型も一致させます。
次の例では、TFigure で宣言されている Draw メソッドが 2 つの下位クラスでオーバーライドされています。
type
TFigure = class
procedure Draw; virtual;
end;
TRectangle = class(TFigure)
procedure Draw; override;
end;
TEllipse = class(TFigure)
procedure Draw; override;
end;
このように宣言されていることを前提として、以下のコードでは、仮想メソッドを呼び出したときの効果を、実行時に実際の型が変わる変数を通じて説明します。
var
Figure: TFigure;
begin
Figure := TRectangle.Create;
Figure.Draw; // calls TRectangle.Draw
Figure.Destroy;
Figure := TEllipse.Create;
Figure.Draw; // calls TEllipse.Draw
Figure.Destroy;
end;
オーバーライドできるのは、virtual メソッドと dynamic メソッドだけです。 ただし、どのメソッドもオーバーロードはできます。詳細は、「メソッドのオーバーロード」を参照してください。
final メソッド
Delphi コンパイラでは、final 仮想メソッドと動的メソッドの概念もサポートしています。 final メソッドの宣言は次の形式です:
function|procedure FunctionName; virtual|dynamic; final;
virtual|dynamic
の構文(2 つのキーワードとその間の |
パイプ)は、virtual と dynamic のキーワードのうちどちらか一方のみを使用することを示す際に使われます。 意味を持っているのはキーワード virtual や dynamic のみで、パイプ シンボルそのものは削除する必要があります。
仮想メソッドまたは動的メソッドにキーワード final が付いた場合、下位クラスではそのメソッドをオーバーライドすることはできません。 final キーワードを使用するかどうかは、クラスの使用目的を記述する上で役に立つ設計上の重要な決定事項です。 また、生成するコードを最適化するためのヒントをコンパイラに与えることにもなります。
メモ: キーワード virtual や dynamic は、final キーワードの前に記述されなければなりません。
例
type
Base = class
procedure TestProcedure; virtual;
procedure TestFinalProcedure; virtual; final;
end;
Derived = class(Base)
procedure TestProcedure; override;
//Ill-formed: E2352 Cannot override a final method
procedure TestFinalProcedure; override;
end;
仮想メソッドと動的メソッドの比較
Delphi for Win32 では、仮想メソッドと動的メソッドは意味的には同等です。ただし、実行時におけるメソッド呼び出しのディスパッチの実装が異なります。つまり、仮想メソッドでは実行速度が最適化されますが、動的メソッドではコード サイズが最適化されます。
一般に、仮想メソッドは、多態的(ポリモーフィック)な動作を実装する最も効率的な方法です。一方、オーバーライド可能なメソッドが基底クラスで多数宣言されており、それらがアプリケーション内の多くの下位クラスによって継承されてはいるものの、たまにしかオーバーライドされないという場合は、動的メソッドが便利です。
メモ: 動的メソッドは、目に見える明確なメリットがある場合にのみ使用してください。通常は、仮想メソッドを使用します。
オーバーライドと隠蔽の比較
継承されたメソッドと同じメソッド識別子およびパラメータ シグネチャをメソッド宣言で指定しても、override 指令が付いていなければ、継承されたメソッドが新しいメソッド宣言によって隠蔽されるだけで、オーバーライドはされません。どちらのメソッドも下位クラスに存在し、そこではメソッド名は静的にバインドされます。以下に例を示します。
type
T1 = class(TObject)
procedure Act; virtual;
end;
T2 = class(T1)
procedure Act; // Act is redeclared, but not overridden
end;
var
SomeObject: T1;
begin
SomeObject := T2.Create;
SomeObject.Act; // calls T1.Act
end;
Reintroduce
reintroduce 指令を付けると、既に宣言されている仮想メソッドが隠蔽されるというコンパイラ警告が表示されなくなります。以下に例を示します。
procedure DoSomething; reintroduce; // The ancestor class also
// has a DoSomething method
継承された仮想メソッドを新しいメソッドで隠蔽する場合は、reintroduce を使用してください。
抽象メソッド
抽象メソッドとは、それを宣言しているクラスに実装がない仮想(動的な)メソッドのことです。抽象メソッドの実装は下位クラスで行われます。抽象メソッドは、virtual または dynamic の後に abstract 指令を付けて宣言する必要があります。以下に例を示します。
procedure DoSomething; virtual; abstract;
abstract メソッドは、そのメソッドをオーバーライドしたクラス、または、そのようなクラスのインスタンスでのみ呼び出すことができます。
クラス メソッド
大半のメソッドは、オブジェクトの個々のインスタンスを操作するため、インスタンス メソッドと呼ばれます。クラス メソッドとは、オブジェクトではなくクラスを操作するメソッド(コンストラクタ以外)です。クラス メソッドには 2 つの種類、「通常クラス メソッド」と「静的クラス メソッド」があります。
通常のクラス メソッド
クラス メソッドを定義する際には、予約語 class を先頭に付ける必要があります。以下に例を示します。
type
TFigure = class
public
class function Supports(Operation: string): Boolean; virtual;
class procedure GetInfo(var Info: TFigureInfo); virtual;
...
end;
クラス メソッドの定義宣言にも、先頭に class を付ける必要があります。以下に例を示します。
class procedure TFigure.GetInfo(var Info: TFigureInfo);
begin
...
end;
クラス メソッドの定義宣言では、Self 識別子は、そのメソッドが呼び出されたクラス(メソッドが定義されているクラスの下位クラスの場合もあり得ます)を表します。クラス メソッドがクラス C で呼び出された場合は、Self は C クラス型になります。したがって、Self を使用して、インスタンス フィールド、インスタンス プロパティ、および通常のメソッド(オブジェクト メソッド)にアクセスすることはできません。ただし、Self を使用して、コンストラクタなどのクラス メソッドを呼び出したり、クラス プロパティやクラス フィールドにアクセスすることは可能です。
クラス メソッドは、クラス参照またはオブジェクト参照を通じて呼び出すことができます。オブジェクト参照を通じて呼び出した場合は、そのオブジェクトのクラスが Self の値になります。
静的クラス メソッド
静的クラス メソッドには、通常のクラス メソッドと同様に、オブジェクト参照なしでアクセスすることができます。通常のクラス メソッドとは異なり、静的クラス メソッドには Self パラメータがまったくありません。また、インスタンス メンバにもアクセスできません (ただし、クラス フィールド、クラス プロパティ、およびクラス メソッドにはアクセスできます)。さらに、静的クラス メソッドは通常のクラス メソッドとは異なり、virtual として宣言することはできません。
static という語を宣言の末尾に付け加えることで、メソッドは静的クラス メソッドになります。たとえば、次のように宣言します。
type
TMyClass = class
strict private
class var
FX: Integer;
strict protected
// Note: Accessors for class properties
// must be declared class static.
class function GetX: Integer; static;
class procedure SetX(val: Integer); static;
public
class property X: Integer read GetX write SetX;
class procedure StatProc(s: String); static;
end;
静的クラス メソッドは、通常のクラス メソッドと同様に、クラス型を通じて(つまり、オブジェクト参照なしで)呼び出すことができます。以下にその例を示します。
TMyClass.X := 17;
TMyClass.StatProc('Hello');
メソッドのオーバーロード
メソッドは、overload 指令を使って再宣言することができます。再宣言されたメソッドのパラメータ シグネチャが上位クラスから継承したメソッドとは異なる場合、継承されたメソッドは隠蔽されずオーバーロードされます。下位クラスでこのメソッドを呼び出すと、呼び出しに使用されたパラメータに一致する実装が起動されます。
仮想メソッドをオーバーロードする場合は、それを下位クラスで再宣言する際に reintroduce 指令を使用してください。以下に例を示します。
type
T1 = class(TObject)
procedure Test(I: Integer); overload; virtual;
end;
T2 = class(T1)
procedure Test(S: string); reintroduce; overload;
end;
...
SomeObject := T2.Create;
SomeObject.Test('Hello!'); // calls T2.Test
SomeObject.Test(7); // calls T1.Test
同じクラス内で、同じ名前のオーバーロード メソッドを複数パブリッシュすることはできません。 実行時型情報の維持管理のため、パブリッシュされたメンバごとに一意な名前が必要です。
type
TSomeClass = class
published
function Func(P: Integer): Integer;
function Func(P: Boolean): Integer; // error
...
プロパティの read 指定子または write 指定子の役目を果たすメソッドは、オーバーロードできません。
オーバーロード メソッドの implementation セクションでは、クラス宣言部で指定したパラメータ リストと同じものを再度指定する必要があります。 オーバーロードの詳細については、「プロシージャと関数のオーバーロード」(「プロシージャと関数(Delphi)」内)を参照してください。
コンストラクタ
コンストラクタは、インスタンス オブジェクトの生成と初期化を行う特別なメソッドです。コンストラクタの宣言は手続きの宣言に似ていますが、それとは異なり、予約語 constructor で始まります。以下に例を示します。
constructor Create;
constructor Create(AOwner: TComponent);
コンストラクタは、デフォルトの register 呼び出し規約に従う必要があります。コンストラクタの宣言では戻り値は指定されませんが、コンストラクタが生成したオブジェクトまたはコンストラクタを呼び出したオブジェクトへの参照が返されます。
クラスは複数のコンストラクタを持つことができますが、通常は 1 つのみです。Create というコンストラクタを呼び出すのが慣例になっています。
オブジェクトを生成するには、クラス型のコンストラクタ メソッドを呼び出します。以下に例を示します。
MyObject := TMyClass.Create;
このコードを実行すると、新しいオブジェクト用の記憶域が割り当てられ、すべての序数フィールドの値が 0 に設定され、すべてのポインタ型フィールドとクラス型フィールドに nil が代入され、そしてすべての文字列フィールドが空になります。コンストラクタの実装で指定されている他のアクションが次に実行されます。通常は、コンストラクタにパラメータとして渡された値に基づいてオブジェクトが初期化されます。最後に、コンストラクタは、新たに割り当てられ初期化されたオブジェクトへの参照を返します。戻り値の型は、コンストラクタの呼び出しで指定されたクラス型と同じです。
クラス参照によって呼び出されたコンストラクタの実行中に例外が発生した場合は、Destroy デストラクタが自動的に呼び出されて未完成のオブジェクトが破棄されます。
(クラス参照ではなく)オブジェクト参照を用いてコンストラクタが呼び出された場合、オブジェクトは生成されません。その代わり、コンストラクタは、指定されたオブジェクトを操作して、コンストラクタの implementation セクションの文だけを実行した後、そのオブジェクトへの参照を返します。コンストラクタは、継承されたコンストラクタを実行するための予約語 inherited を付けてオブジェクト参照で呼び出されるのが一般的です。
クラス型とそのコンストラクタの例を以下に示します。
type
TShape = class(TGraphicControl)
private
FPen: TPen;
FBrush: TBrush;
procedure PenChanged(Sender: TObject);
procedure BrushChanged(Sender: TObject);
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
...
end;
constructor TShape.Create(Owner: TComponent);
begin
inherited Create(Owner); // Initialize inherited parts
Width := 65; // Change inherited properties
Height := 65;
FPen := TPen.Create; // Initialize new fields
FPen.OnChange := PenChanged;
FBrush := TBrush.Create;
FBrush.OnChange := BrushChanged;
end;
通常、コンストラクタではまず、継承したコンストラクタを呼び出して、オブジェクトの継承したフィールドを初期化します。その次に、コンストラクタは下位クラスで追加されたフィールドを初期化します。コンストラクタでは新規オブジェクト用に割り当てた記憶域を必ずクリアするため、フィールドの初期値は 0(順序型の場合)、nil(ポインタ型とクラス型の場合)、空の文字列(文字列型の場合)、Unassigned(バリアント型の場合)のいずれかになります。したがって、0 以外の値や空の文字列以外の値に初期化する場合を除き、コンストラクタの実装でフィールドを初期化する必要はありません。
クラス型の識別子を通じて呼び出す場合、virtual として宣言したコンストラクタは静的コンストラクタと同等です。ただし、仮想コンストラクタをクラス参照型と組み合わせると、多態的(ポリモーフィック)なオブジェクト生成、つまりコンパイル時に型がわからないオブジェクトの生成が可能になります (「クラス参照」を参照)。
デストラクタ
デストラクタは、呼び出し元のオブジェクトを破棄し、そのメモリを解放する特別なメソッドです。デストラクタの宣言は手続きの宣言に似ていますが、それとは異なり、予約語 destructor で始まります。以下に例を示します。
destructor SpecialDestructor(SaveData: Boolean);
destructor Destroy; override;
Win32 のデストラクタは、デフォルトの register 呼び出し規約に従う必要があります。1 つのクラスに複数のデストラクタを持たせることも可能ですが、各クラスでは、継承した Destroy メソッドをオーバーライドし、それ以外のデストラクタを宣言しないようにすることをお勧めします。
デストラクタを呼び出すには、インスタンス オブジェクトを参照する必要があります。以下に例を示します。
MyObject.Destroy;
デストラクタが呼び出されると、デストラクタの実装で指定された処理がまず実行されます。通常、これらの処理としては、埋め込みオブジェクトの破棄と、そのオブジェクトにより割り当てられたリソースの解放があります。その後、そのオブジェクト用に割り当てられた記憶域が破棄されます。
デストラクタ実装の例を以下に示します。
destructor TShape.Destroy;
begin
FBrush.Free;
FPen.Free;
inherited Destroy;
end;
デストラクタの実装で最後に指定されている処理は通常、継承したデストラクタを呼び出してオブジェクトの継承したフィールドを破棄することです。
オブジェクトの生成中に例外が発生した場合は、Destroy が自動的に呼び出され、未完成のオブジェクトが破棄されます。つまり、部分的にしか完成していないオブジェクトを破棄するために Destroy を用意しておく必要があるということです。コンストラクタは新しいオブジェクトのフィールド値を 0 または空に設定してからそれ以外の処理を実行するため、未完成なオブジェクトのクラス型フィールドとポインタ型フィールドは必ず nil になります。したがって、デストラクタでは、クラス型フィールドやポインタ型フィールドを操作する前に、nil 値でないかどうかをチェックするのがよいでしょう。Destroy の代わりに Free メソッド(TObject で定義)を呼び出すと、オブジェクトを破棄する前に nil 値のチェックを簡便に行えます。
クラス コンストラクタ
クラス コンストラクタは、特別なクラス メソッドで、開発者はアクセスできません。クラス コンストラクタの呼び出しは、そのクラスが定義されているユニットの initialization セクションに、コンパイラが自動的に挿入します。通常クラス コンストラクタは、クラスの静的フィールドを初期化したり、自身や他のクラスのインスタンスが正常に機能する前に必要となるような初期化を実行します。initialization セクションにクラスの初期化コードを記述しても同じ結果が得られますが、クラス コンストラクタには、最終的なバイナリ ファイルにどのクラスが含まれるべきか、およびバイナリ ファイルからどのクラスを削除すべきかを、コンパイラが判断するのを助けるというメリットがあります。
次に、クラス フィールドを初期化する通常の方法を示します。
type
TBox = class
private
class var FList: TList<Integer>;
end;
implementation
initialization
{ Initialize the static FList member }
TBox.FList := TList<Integer>.Create();
end.
この方法には大きなデメリットがあります。アプリケーションに、TBox が宣言されているユニットが含まれるにも関わらず、実際には TBox クラスが使われない可能性があります。この例では、TBox クラスが initialization セクションで参照されているので、結果として出力されるバイナリに、このクラスが含まれてしまいます。この問題を軽減するには、次のようなクラス コンストラクタの使用を検討します。
type
TBox = class
private
class var FList: TList<Integer>;
class constructor Create;
end;
implementation
class constructor TBox.Create;
begin
{ Initialize the static FList member }
FList := TList<Integer>.Create();
end;
end.
この例では、コンパイラがアプリケーションのどこかで TBox が実際に使用されているかどうか確認し、使用されている場合は、クラス コンストラクタの呼び出しがユニットの initialization セクションに自動的に追加されます。
メモ: コンパイラはクラスの初期化順を調整しますが、複雑な状況では、順番がランダムになる可能性があります。これが発生するのは、あるクラスのクラス コンストラクタが、他のクラスの状態に依存していて、この他のクラスも 1 つ目のクラスに依存している場合です。
メモ: ジェネリック クラスまたはレコードのクラス コンストラクタは、複数回実行される可能性があります。この場合にクラス コンストラクタが実行される正確な回数は、ジェネリック型の特化バージョン番号によって異なります。たとえば、特化した TList<String> クラスのクラス コンストラクタは、同一のアプリケーション内で複数回実行される可能性があります。
クラス デストラクタ
クラス デストラクタは、クラス コンストラクタとは逆に、クラスの終了処理部で実行されます。クラス デストラクタには、目的が終了処理であるという点以外は、クラス コンストラクタと同じメリットがあります。
次の例は、クラス コンストラクタの説明で示した例に、終了処理ルーチンを追加したものです。
type
TBox = class
private
class var FList: TList<Integer>;
class constructor Create;
class destructor Destroy;
end;
implementation
class constructor TBox.Create;
begin
{ Initialize the static FList member }
FList := TList<Integer>.Create();
end;
class destructor TBox.Destroy;
begin
{ Finalize the static FList member }
FList.Free;
end;
end.
メモ: ジェネリック クラスまたはレコードのクラス デストラクタは、複数回実行される可能性があります。この場合にクラス デストラクタが実行される正確な回数は、ジェネリック型の特化バージョン番号によって異なります。たとえば、特化した TList<String> クラスのクラス デストラクタは、同一のアプリケーション内で複数回実行される可能性があります。
メッセージ メソッド
メッセージ メソッドは、動的にディスパッチされるメッセージへの応答を実装したものです。メッセージ メソッドの構文は、すべてのプラットフォームでサポートされています。 VCL では、メッセージ メソッドを使用して Windows メッセージに応答します。
メッセージ メソッドを作成するには、メソッド宣言に message 指令を付け、その後にメッセージ ID を 1 ~ 49151 の範囲の整数定数で指定します。 VCL コントロールにおけるメッセージ メソッドの場合、この整数定数には、Messages ユニットに定義されている Win32 メッセージ ID とそれらに対応するレコード型のいずれかを使用できます。 メッセージ メソッドは、ただ 1 つの var パラメータを取る手続きでなければなりません。
以下に例を示します。
type
TTextBox = class(TCustomControl)
private
procedure WMChar(var Message: TWMChar); message WM_CHAR;
...
end;
メッセージ メソッドに override 指令を付けて、継承したメッセージ メソッドをオーバーライドする必要はありません。実際には、オーバーライド対象のメソッドと同じメソッド名やパラメータの型を指定する必要もありません。メソッドがどのメッセージに応答するかと、オーバーライドであるかどうかは、メッセージ ID だけで決まるのです。
メッセージ メソッドの実装
次の例に示すように、メッセージ メソッドの実装からは、継承したメッセージ メソッドを呼び出すことができます。
procedure TTextBox.WMChar(var Message: TWMChar);
begin
if Message.CharCode = Ord(#13) then
ProcessEnter
else
inherited;
end;
inherited 文は、クラス階層を逆方向にたどって、現在のメソッドと同じ ID を持つ最初のメッセージ メソッドを探し、そのメソッドにメッセージ レコードを自動的に渡してこれを呼び出します。当該 ID に対応するメッセージ メソッドが上位クラスに実装されていない場合、inherited は TObject に元々定義されている DefaultHandler を呼び出します。
TObject における DefaultHandler の実装は、何も実行せずにただ戻るだけです。クラスでは、DefaultHandler をオーバーライドすることで、メッセージのデフォルト処理を独自に実装することができます。Win32 では、コントロールの DefaultHandler メソッドは Win32 API の DefWindowProc を呼び出します。
メッセージのディスパッチ
メッセージ ハンドラが直接呼び出されることは、ほとんどありません。その代わり、メッセージは、TObject から継承された以下の Dispatch メソッドを使ってオブジェクトにディスパッチされます。
procedure Dispatch(var Message);
Dispatch に渡す Message パラメータはレコードであり、その最初のエントリは、メッセージ ID を格納する Word 型のフィールドでなければなりません。
Dispatch は(呼び出し元のオブジェクトのクラスを出発点として)クラス階層を逆方向にたどり、渡された ID に対応する最初のメッセージ メソッドを探して呼び出します。当該 ID に対応するメッセージ メソッドが見つからない場合、Dispatch は DefaultHandler を呼び出します。