はじめに
TListの不便さ
また煽り文句のようなタイトルですが実際にDelphiでTListクラスを継承して使っている人は多いはずです。私はつい最近になってTListクラスを使うことを止めました。TListクラスよりももっと便利なクラスを作ったからです
2025/07/18
外部ユニットへの依存性を無くすために作り直しました
まずはTListクラスの不便さを知ってください
TListを継承する方法
type
TSampleList = class(TList)
public
// 要素取得用のキャスト付きアクセサ
function Items(Index: Integer): TSampleItem;
procedure Add(Item: TSampleItem);
procedure Clear; override;
procedure Assign(Source: TSampleList);
end;
{ TSampleList }
procedure TSampleList.Add(Item: TSampleItem);
begin
inherited Add(Item);
end;
procedure TSampleList.Clear;
var
i: Integer;
begin
for i := 0 to Count - 1 do
TSampleItem(Get(i)).Free;
inherited Clear;
end;
function TSampleList.Items(Index: Integer): TSampleItem;
begin
Result := TSampleItem(Get(Index));
end;
procedure TSampleList.Assign(Source: TSampleList);
var
i: Integer;
SrcItem, NewItem: TSampleItem;
begin
if Source = nil then Exit;
Clear;
for i := 0 to Source.Count - 1 do
begin
SrcItem := Source.Items(i);
NewItem := TSampleItem.Create;
NewItem.Assign(SrcItem);
Add(NewItem);
end;
end;
内包する方法だと
type
TSampleItem = class(TRTTIPersistentIni)
private
FName : string;
FEnabled : Boolean;
public
published
property Name : string read FName write FName;
property Enabled : Boolean read FEnabled write FEnabled;
end;
type
TSampleList = class
private
FList: TList; // 生のTListを使う
public
constructor Create;
destructor Destroy; override;
function Count: Integer;
function Items(Index: Integer): TSampleItem;
procedure Add(Item: TSampleItem);
procedure Clear;
property List: TList read FList;
end;
{ TSampleList }
constructor TSampleList.Create;
begin
inherited Create;
FList := TList.Create;
end;
destructor TSampleList.Destroy;
var
i: Integer;
begin
// 明示的に各要素を解放する必要あり
for i := 0 to FList.Count - 1 do
TObject(FList[i]).Free;
FList.Free;
inherited;
end;
function TSampleList.Count: Integer;
begin
Result := FList.Count;
end;
function TSampleList.Items(Index: Integer): TSampleItem;
begin
Result := TSampleItem(FList[Index]);
end;
procedure TSampleList.Add(Item: TSampleItem);
begin
FList.Add(Item);
end;
procedure TSampleList.Clear;
var
i: Integer;
begin
for i := 0 to FList.Count - 1 do
TObject(FList[i]).Free;
FList.Clear;
end;
親の顔よりも見たコードです。
そしてやっかいなのが何かのクラスをリスト化する度にこのコードを移植しなければならないのです。
なので下手をすると本当に書かなければいけないコードよりもTListのためのコードを書いている時間の方が長い。そう感じることもあるでしょう。
なので作りました。
その名もTRTTIPersistentIniListクラスです。
配布
そんなものを使わなくったってオブジェクトリストを使えばいいじゃない?
そう思う人へも朗報です。
オブジェクトリストにバグっぽいのがあったのでそれも修正して使いやすくなってます。
宣言部
type
TSampleItem = class(TRTTIPersistentIni)
private
FName : string;
FEnabled : Boolean;
public
published
property Name : string read FName write FName;
property Enabled : Boolean read FEnabled write FEnabled;
end;
type
TSampleList = class(TRTTIPersistentIniList<TSampleItem>)
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
文字列と真偽型を持つTSampleItemをリスト化するのがTSampleListです。
その関連性を示すためにTSampleListのclass内の宣言にもTSampleItemがあります。
クラス名をTSampleItemから変更する場合はここも変えてください。
宣言部にメソッドが無いのでこれで完成したことになります。
そうです。もうなにも書かなくても良いのです!!
生成と破棄
FList1 := TSampleList.Create();
FList2 := TSampleList.Create();
FList2.Free;
FList1.Free;
生成と破棄の方法はTListで設計した場合と同じです。
追加
var
Item : TSampleItem;
begin
item := FList1.Add;
end;
これまでのListの場合はAddをオーバーライドしていたのでこうでした。
これがTRTTIPersistentIniListを使う場合は
var
Item : TSampleItem;
begin
item := FList1.AddNew;
end;
こうなります。
Addはこのクラス内や下位クラスで呼ばれることを考慮して残しています。
挿入
var
Item : TSampleItem;
begin
item := FList1.InsertNew(1);
end;
同じように後ろにNewが付きます。
削除
FList1.Delete(1);
削除はそのまま使えます。
削除(オブジェクト指定)
var
Item : TSampleItem;
begin
FList1.DeleteItem(Item);
end;
このオブジェクトをリストから消したいという場合はDeleteItemを使用します。
代入 Assign
リストを代入するとき
procedure TSampleList.Assign(Source: TSampleList);
var
SrcItem, NewItem: TSampleItem;
i: Integer;
begin
if Source = nil then Exit;
// 既存の内容をクリア(手動で Free も必要)
Clear;
// 1つずつコピーして追加
for i := 0 to Source.Count - 1 do
begin
SrcItem := Source.Items(i);
NewItem := TSampleItem.Create;
NewItem.Assign(SrcItem);
Add(NewItem);
end;
end;
こういう処理が必要でした。
さらにTSampleItemにも
procedure TSampleItem.Assign(Source: TPersistent);
var
Src: TSampleItem;
begin
if Source is TSampleItem then
begin
Src := TSampleItem(Source);
FName := Src.FName;
FEnabled := Src.FEnabled;
end
else
inherited;
end;
こういう処理が必要です。
ではTRTTIPersistentIniListではどう実装するのか?
実装する必要がありません!!
FList1 := TSampleList.Create();
FList2 := TSampleList.Create();
FList2.Assign(FList1);
FList2.Free;
FList1.Free;
なんとAssign機能を内包しています。
入れ替え Exchange
ExchangeはTListには用意されています。オブジェクトリストにもExchangeがあって使えそうなのですがバグがあったので直しました。
メソッドを再定義しているのでそのままExchangeを使ってください。
ファイルに保存 SaveToFile
リストとその要素をファイルに保存する機能があります。
FList1 := TSampleList.Create();
FList1.Filename := 'ファイル名';
FList1.SaveToFile();
FList1.Free;
ファイル名は生成した直後に決めた方が楽なのでプロパティにしています。
2件のデータを保存すると
[0]
Name=UserName1
Enabled=1
[1]
Name=UserName2
Enabled=0
こういうファイルになります。わかりやすいですね。
ファイルから読み込み LoadFromFile
リストとその要素をファイルから読み込む機能もあります。
FList1 := TSampleList.Create();
FList1.Filename := 'ファイル名';
FList1.LoadFromFile();
FList1.Free;
中でファイルの存在チェックも行っています。
ファイルが無い場合Clearされませんので初期値が維持されます。
ライセンス
MITライセンスなので、配布や改変など自由に行ってください