3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

君たちはまだTListを使っているのか?

Last updated at Posted at 2025-07-16

はじめに

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ライセンスなので、配布や改変など自由に行ってください

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?