2012年11月2日金曜日

Record クイズ

突然ですがクイズです。

下記の様なレコード型 TBar があります。

■ リスト1
TBar = record
FBaz: Boolean;
constructor Create(const iBaz: Boolean);
procedure SetBaz(const iBaz: Boolean);
end;
 
// レコード型のコンストラクタは引数がないといけない
constructor TBar.Create(const iBaz: Boolean);
begin
SetBaz(iBaz);
end;
 
// FBaz を設定する Accesser
procedure TBar.SetBaz(const iBaz: Boolean);
begin
FBaz := iBaz;
end;

TBar をリストに持つ TFoo があります。

■ リスト2
TFoo = class
FBars: TList<TBar>;
procedure Check;
constructor Create; reintroduce;
destructor Destroy; override;
end;
 
constructor TFoo.Create;
var
Bar: TBar;
begin
inherited;
 
// リストを生成
FBars := TList<TBar>.Create;
 
// FBaz = False で生成
FBars.Add(TBar.Create(False));
 
// FBaz = True に設定
for Bar in FBars do
Bar.SetBaz(True);
end;
 
destructor TFoo.Destroy;
begin
FBars.Free;
 
inherited;
end;

では、このとき、下記のコードを実行すると何が出力されるでしょうか?

■ リスト3
begin
with TFoo.Create do
try
if (FBars[0].FBaz) then
Writeln('TRUE')
else
Writeln('FALSE');
 
Readln;
finally
Free;
end;
end.
























こんな風に書いているから丸わかりでしょうが、答えは FALSE です。

何故かと言うとレコードはあくまでレコードだから、です。
クラスのようにメソッドを持てるようになりましたが、(当然ですが)レコード型の代入は値のコピーが渡されるに過ぎません。

クラスなら、変数に保持されているのはポインタですので、値を変更すれば元の値が変わります

なので「リスト2」にあった下記のコードは

var
Bar: TBar;
begin
(略)
// ローカル変数 Bar の FBaz が設定されただけで
// FBars[0] の Bar が設定されるわけではない!
for Bar in FBars do
Bar.SetBaz(True);
end;

あくまでローカル変数に対しての操作になります。
ローカル変数ですのでメソッドを抜けたら、破棄されます。

クラス感覚で、レコード型を使うと痛い目にあうよ!というお話しでした。

つまり痛い目に遭ったわけです……
TStyleProvider が更新されております……

ちなみに、SetBaz というメソッドを作ったのは、下記の様に代入エラーが出るからです。
これで気づきました。

for Bar in FBars do
Bar.FBaz := True; // 代入できない左辺値エラー

0 件のコメント:

コメントを投稿