プロパティ(Delphi)
クラスとオブジェクト:インデックス への移動
このトピックで扱う内容は以下のとおりです。
- プロパティへのアクセス
- 配列プロパティ
- インデックス指定子
- 格納指定子
- プロパティのオーバーライドと再宣言
- クラス プロパティ
プロパティの概要
プロパティは、フィールドと同様に、オブジェクトの属性を定義するものです。ただし、フィールドが内容を調べたり変更できる単なる格納場所であるのに対して、プロパティは特定のアクションをデータの読み書きに結び付けています。プロパティによって、オブジェクトの属性に対するアクセスを制御したり、属性を計算することができます。
プロパティの宣言には、名前および型の指定と、少なくとも 1 つのアクセス指定子が含まれます。プロパティ宣言の構文を次に示します。
property propertyName[indexes]: type index integerConstant specifiers;
ここで
- propertyName は任意の有効な識別子です。
- [[indexes] は省略可能で、一連のパラメータ宣言をセミコロンで区切って指定します。それぞれのパラメータ宣言は、"識別子1, ..., 識別子n: 型" という形式になります。詳細は、この後の「配列プロパティ」の節を参照してください。
- type は、定義済みの型識別子か、前で宣言された型識別子でなければなりません。そのため、property Num: 0..9 ... のようなプロパティ宣言は無効です。
- index integerConstant 句は省略可能です。詳細は、この後の「インデックス指定子」の節を参照してください。
- specifiers は、read、write、stored、default(または nodefault)、implements の指定子の列です。各プロパティ宣言には、少なくとも 1 つ、read または write の指定子を含めなければなりません。
プロパティの定義にはアクセス指定子が含まれます。フィールドと違って、プロパティは var パラメータとして渡すこともできなければ、@ 演算子を使用することもできません。それは、プロパティがメモリ内に存在するとは限らないためです。たとえば、プロパティがデータベースから値を取得したり乱数値を生成するような read メソッドを持つ場合などが考えられます。
プロパティへのアクセス
プロパティはどれも、read 指定子か write 指定子、あるいはその両方を持ちます。これらはアクセス指定子というもので、次の形式で指定します。
read fieldOrMethod
write fieldOrMethod
fieldOrMethod は、プロパティと同じクラスかその上位クラスで宣言された、フィールドかメソッドの名前です。
- fieldOrMethod が同じクラス内で宣言されている場合には、その宣言はプロパティ宣言より前になければなりません。上位クラス内で宣言されている場合には、プロパティを宣言している下位クラスから見えるものでなければなりません。つまり、プライベート フィールドであったり、別のユニット内で宣言された上位クラスのメソッドであってはなりません。
- fieldOrMethod がフィールドの場合、そのフィールドはプロパティと同じ型でなければなりません。
- fieldOrMethod がメソッドの場合、そのメソッドは dynamic であってはならず、virtual であってもオーバーロードはできません。さらに、公開プロパティのアクセス メソッドでは、デフォルトの register 呼び出し規約を使用しなければなりません。
- read 指定子の fieldOrMethod がメソッドである場合、そのメソッドは、パラメータを取らず、結果型がプロパティの型と同じである関数でなければなりません。(ただし、インデックス付きプロパティや配列プロパティのアクセス メソッドは例外です。)
- write 指定子の fieldOrMethod がメソッドである場合、そのメソッドは、プロパティと同じ型の値または const パラメータを 1 つだけ取る手続きでなければなりません(配列プロパティまたはインデックス付きプロパティの場合には複数になります)。
たとえば、次のような宣言があるとします。
property Color: TColor read GetColor write SetColor;
GetColor メソッドは次のような宣言になっていなければなりません。
function GetColor: TColor;
また、SetColor メソッドは次のいずれかの宣言になっていなければなりません。
procedure SetColor(Value: TColor);
procedure SetColor(const Value: TColor);
(もちろん、SetColor のパラメータの名前は Value でなくても構いません。)
式の中でプロパティを参照すると、その値は read 指定子で指定したフィールドまたはメソッドを使って読み取られます。代入文の中でプロパティを参照すると、その値は write 指定子で指定したフィールドまたはメソッドを使って書き込まれます。
次の例では、Heading という公開プロパティを持つ TCompass というクラスを宣言しています。Heading の値は、FHeading というフィールドを通じて読み取られ、SetHeading という手続きを通じて書き込まれます。
type
THeading = 0..359;
TCompass = class(TControl)
private
FHeading: THeading;
procedure SetHeading(Value: THeading);
published
property Heading: THeading read FHeading write SetHeading;
...
end;
このように宣言されている場合、次の文は
if Compass.Heading = 180 then GoingSouth;
Compass.Heading := 135;
次の文と同じ意味になります。
if Compass.FHeading = 180 then GoingSouth;
Compass.SetHeading(135);
TCompass クラスでは、Heading プロパティの読み取りに関連付けられているアクションはありません。read 操作では FHeading フィールドに格納されている値を取得します。逆に、Heading プロパティに対する値の代入は、SetHeading メソッドの呼び出しに変換されます。このメソッドではおそらく、新しい値を FHeading フィールドに格納するだけでなく、他のアクションも実行します。たとえば、SetHeading は次のように実装されている可能性があります。
procedure TCompass.SetHeading(Value: THeading);
begin
if FHeading <> Value then
begin
FHeading := Value;
Repaint; // update user interface to reflect new value
end;
end;
宣言に read 指定子しか含まれないプロパティは読み取り専用プロパティに、宣言に write 指定子しか含まれないプロパティは書き込み専用プロパティになります。読み取り専用プロパティに値を代入したり、式の中で書き込み専用プロパティを使用すると、エラーになります。
配列プロパティ
配列プロパティは、インデックス付きプロパティです。リスト内の項目や、コントロールの子コントロール、ビットマップのピクセルなどを表すことができます。
配列プロパティの宣言には、インデックスの名前と型を指定するパラメータ リストが含まれます。たとえば、次のようになります。
property Objects[Index: Integer]: TObject read GetObject write SetObject;
property Pixels[X, Y: Integer]: TColor read GetPixel write SetPixel;
property Values[const Name: string]: string read GetValue write SetValue;
インデックス パラメータ リストの形式は、パラメータ宣言が丸かっこではなく角かっこで囲まれている以外は、手続きや関数のパラメータ リストと同じです。順序型のインデックスしか使えない配列とは異なり、配列プロパティではどのような型のインデックスでも使用可能です。
配列プロパティのアクセス指定子には、フィールドではなくメソッドを指定しなければなりません。read 指定子のメソッドは、プロパティのインデックス パラメータ リストと同じ型で同じ数のパラメータを同じ順番で取り、プロパティの型と同じ結果型を返す関数でなければなりません。write 指定子のメソッドは、プロパティのインデックス パラメータ リストと同じ型で同じ数、かつ同じ順番のパラメータと、さらに 1 つ追加でプロパティと同じ型の値または const パラメータを取る手続きでなければなりません。
たとえば、上記の配列プロパティのアクセス メソッドは、次のように宣言されます。
function GetObject(Index: Integer): TObject;
function GetPixel(X, Y: Integer): TColor;
function GetValue(const Name: string): string;
procedure SetObject(Index: Integer; Value: TObject);
procedure SetPixel(X, Y: Integer; Value: TColor);
procedure SetValue(const Name, Value: string);
配列プロパティには、プロパティ識別子にインデックスを指定してアクセスします。たとえば、次の文は
if Collection.Objects[0] = nil then Exit;
Canvas.Pixels[10, 20] := clRed;
Params.Values['PATH'] := 'C:\BIN';
次の文と同じ意味になります。
if Collection.GetObject(0) = nil then Exit;
Canvas.SetPixel(10, 20, clRed);
Params.SetValue('PATH', 'C:\BIN');
配列プロパティの定義の後には、default 指令を続けることができます。そうすると、配列プロパティはクラスのデフォルト プロパティになります。たとえば、次のようになります。
type
TStringArray = class
public
property Strings[Index: Integer]: string ...; default;
...
end;
クラスにデフォルト プロパティがあると、object[index] という省略形を使ってそのプロパティにアクセスすることができます。これは object.property[index] と同じ意味です。たとえば、上記のように宣言されている場合、StringArray.Strings[7] は StringArray[7] と省略することができます。1 つのクラスでは、指定したシグネチャ(配列パラメータ リスト)を持つデフォルト プロパティを 1 つしか持つことができませんが、デフォルト プロパティをオーバーロードすることは可能です。コンパイラはプロパティに対するバインディングを常に静的に行っているため、下位クラスでデフォルト プロパティを変更または隠蔽すると、予期しない動作が起きることがあります。
インデックス指定子
インデックス指定子を使用すると、異なる値を表す複数のプロパティに対して同じメソッドでアクセスすることが可能になります。インデックス指定子は、index 指令の後に -2147483647 ~ 2147483647 の間の整数定数を続けたものです。プロパティにインデックス指定子が指定されている場合、その read 指定子および write 指定子には、フィールドではなくメソッドを記載する必要があります。たとえば、次のようになります。
type
TRectangle = class
private
FCoordinates: array[0..3] of Longint;
function GetCoordinate(Index: Integer): Longint;
procedure SetCoordinate(Index: Integer; Value: Longint);
public
property Left: Longint index 0 read GetCoordinate
write SetCoordinate;
property Top: Longint index 1 read GetCoordinate
write SetCoordinate;
property Right: Longint index 2 read GetCoordinate
write SetCoordinate;
property Bottom: Longint index 3 read GetCoordinate
write SetCoordinate;
property Coordinates[Index: Integer]: Longint
read GetCoordinate
write SetCoordinate;
...
end;
インデックス指定子を持つプロパティのアクセス メソッドには、整数型の値パラメータが 1 つ余分に必要です。そのパラメータは、read 関数の場合には最後の、write 手続きの場合には最後から 2 番目(プロパティ値を指定するパラメータの前)のパラメータでなければなりません。プログラムでプロパティにアクセスすると、プロパティの整数定数が自動的にアクセス メソッドに渡されます。
上のように宣言されている場合、Rectangle が TRectangle 型であれば、次の文は
Rectangle.Right := Rectangle.Left + 100;
次の文と同じ意味になります。
Rectangle.SetCoordinate(2, Rectangle.GetCoordinate(0) + 100);
格納指定子
stored、default、nodefault の指令(省略可能)は、格納指定子と呼ばれます。これはプログラムの動作に影響するものではなく、公開プロパティの値をフォーム ファイルに保存するかどうかを制御するためのものです。
stored 指令の後には、True、False、Boolean フィールドの名前、パラメータを持たず Boolean 値を返すメソッドの名前のいずれかを、指定しなければなりません。たとえば、次のようになります。
property Name: TComponentName read FName write SetName stored False;
プロパティに stored 指令が指定されていない場合は、stored True が指定された場合と同じ扱いになります。
default 指令の後には、プロパティと同じ型の定数を続けなければなりません。たとえば、次のようになります。
property Tag: Longint read FTag write FTag default 0;
新しいものを指定するのではなく継承した default の値をオーバーライドするには、nodefault 指令を使用します。default および nodefault の指令は、順序型と集合型(ただし集合の基底型の上限および下限が 0 ~ 31 の間の順序値である場合のみ)でしかサポートされていません。これらの型のプロパティの宣言に default も nodefault も指定されていない場合は、nodefault が指定された場合と同じ扱いになります。実数、ポインタ、文字列の場合には、それぞれ 0、nil、''(空の文字列)が暗黙の default 値となります。
コンポーネントの状態を保存するときには、コンポーネントの公開プロパティの格納指定子が調べられます。プロパティの現在の値が default の値と異なっていて(あるいは default の値が指定されていなくて)、stored 指定子が True の場合には、プロパティの値は保存されます。それ以外の場合には、プロパティの値は保存されません。
配列プロパティでは格納指定子はサポートされていません。配列プロパティの宣言で default 指令が使われた場合には、別の意味になります。上述の「配列プロパティ」の節を参照してください。
プロパティのオーバーライドと再宣言
型を指定しないプロパティ宣言は、プロパティ オーバーライドと呼ばれます。プロパティ オーバーライドを行うと、プロパティが継承した可視性や指定子を変更することができます。最も単純なオーバーライドは property という予約語の後に継承したプロパティ識別子を続けただけのもので、この形式を使ってプロパティの可視性を変更することができます。たとえば、上位クラスでプロパティを protected と宣言しているときに、派生クラスの public または published のセクションでそのプロパティを再宣言することができます。プロパティ オーバーライドには read、write、stored、default、nodefault の指令を含めることができます。これらの指令は、それぞれ対応する継承した指令をオーバーライドします。オーバーライドによって、継承したアクセス指定子を置き換えたり、足りない指定子を追加したり、プロパティの可視性を向上させることができますが、アクセス指定子を削除したり、プロパティの可視性を低下させることはできません。オーバーライドには、implements 指令を含めて、継承した実装インターフェイスを削除することなく新しいものを追加することができます。
次の宣言はプロパティ オーバーライドの使い方の例です。
type
TAncestor = class
...
protected
property Size: Integer read FSize;
property Text: string read GetText write SetText;
property Color: TColor read FColor write SetColor stored False;
...
end;
type
TDerived = class(TAncestor)
...
protected
property Size write SetSize;
published
property Text;
property Color stored True default clBlue;
...
end;
Size のオーバーライドでは、write 指定子を追加して、プロパティを変更できるようにしています。Text および Color のオーバーライドでは、プロパティの可視性を protected から published に変更しています。Color のプロパティ オーバーライドでは、値が clBlue でない場合にそのプロパティを保存しなければならないとも指定しています。
プロパティの再宣言に型識別子を含めると、継承したプロパティをオーバーライドするのではなく隠蔽してしまいます。つまり、継承したプロパティと同じ名前の新しいプロパティが作られることになります。プロパティ宣言で型を指定する場合には、必ず完全な宣言をする必要があり、そのため、少なくとも 1 つのアクセス指定子を含める必要があります。
派生クラスでプロパティが隠蔽された場合もオーバーライドされた場合も、プロパティのルックアップは必ず静的に行われます。つまり、オブジェクトを識別するために使われる変数がどの型で宣言されたか(コンパイル時の型)によって、プロパティ識別子の解釈が決まることになります。そのため、次のコードを実行した後で MyObject.Value の値の読み取りや代入を行うと、MyObject が保持しているのが TDescendant のインスタンスであるにも関わらず、Method1 や Method2 が呼び出されます。しかし、MyObject を TDescendant にキャストすると、下位クラスのプロパティやアクセス指定子にアクセスすることができます。
type
TAncestor = class
...
property Value: Integer read Method1 write Method2;
end;
TDescendant = class(TAncestor)
...
property Value: Integer read Method3 write Method4;
end;
var MyObject: TAncestor;
...
MyObject := TDescendant.Create;
クラス プロパティ
クラス プロパティには、オブジェクト参照がなくてもアクセスすることができます。クラス プロパティのアクセサは、静的クラス メソッドまたはクラス フィールドとして宣言しなければなりません。クラス プロパティは、class property というキーワードを付けて宣言します。クラス プロパティは published にすることができず、stored や default の値も定義できません。
class var ブロック宣言を使用すると、クラス宣言内に静的クラス フィールドのブロックを作成することができます。class var の後に宣言されたフィールドはどれも、静的な格納属性を持ちます。class var ブロックは、次のいずれかで終了します。
- 別の class var 宣言
- 手続きまたは関数(メソッド)の宣言(クラスやクラス関数を含む)
- プロパティの宣言(クラス プロパティを含む)
- コンストラクタやデストラクタの宣言
- 可視性指定子(public、private、protected、published、strict private、strict protected)
たとえば、次のようになります。
type
TMyClass = class
strict private
class var // Note fields must be declared as class fields
FRed: Integer;
FGreen: Integer;
FBlue: Integer;
public // ends the class var block
class property Red: Integer read FRed write FRed;
class property Green: Integer read FGreen write FGreen;
class property Blue: Integer read FBlue write FBlue;
end;
上記のクラス プロパティには、次のコードでアクセスすることができます。
TMyClass.Red := 0;
TMyClass.Blue := 0;
TMyClass.Green := 0;