Genericsの型キャスト?
あるクラスとそれを管理するリストを考えた場合、Genericsでリストを作成するといろんな型を扱うことができていいなと思い、TList<T>を使って下記のようなプログラムを書きました。
TMusic = class(TObject)
private
FTitle: String;
FArtist: String;
public
constructor Create(Title, Artist: String);
procedure SaveToStream(var Stream: TStream);
end;
TMyList<T> = class(TList<T>)
public
{ 略 }
procedure SaveToStream(var Stream: TStream);
end;
しかし、下記の部分の型キャストができませんでした。かと言って ここを特定の型で書いてしまうとTListと変わらなくなり、汎用性に欠けます。
procedure TMyList<T>.SaveToStream(var Stream: TStream);
var
I: Integer;
begin
Stream.WriteBuffer(Self.Count, SizeOf(Integer));
for I := 0 to Self.Count -1 do
T.SaveToStream(Stream); // ←ここの型キャストができない。
end;
試行錯誤の結果、下記のようなプログラムにすることで、型キャストの問題をクリアできました。
TMyBase = class(TPersistent)
public
procedure SaveToStream(var Stream: TStream); virtual; abstract;
procedure LoadFromStream(Stream: TStream); virtual; abstract;
end;
// TMyBaseから派生したクラスを扱うリスト
TMyList<T: TMyBase, constructor> = class(TList<T>)
public
destructor Destroy; override;
procedure ClearItems;
procedure SaveToStream(var Stream: TStream);
procedure LoadFromStream(Stream: TStream);
procedure SaveToFile(const FileName: String);
procedure LoadFromFile(const FileName: String);
end;
TMusic = class(TMyBase)
private
FTitle: String;
FArtist: String;
public
constructor Create; overload;
constructor Create(Title, Artist: String); overload;
procedure Assign(Source: TPersistent); override;
procedure SaveToStream(var Stream: TStream); override;
procedure LoadFromStream(Stream: TStream); override;
published
property Title: String read FTitle write FTitle;
property Artist: String read FArtist write FArtist;
end;
{ TMyList<T> }
destructor TMyList<T>.Destroy;
begin
ClearItems;
inherited;
end;
procedure TMyList<T>.ClearItems;
var
P: Pointer;
V: T;
I,K: Integer;
begin
for I := 0 to Self.Count -1 do
begin
V := Self.Items[I];
FreeAndNil(V);
end;
Self.Clear;
end;
procedure TMyList<T>.LoadFromStream(Stream: TStream);
var
I,Cnt: Integer;
V: T;
begin
ClearItems;
Cnt := ReadIntegerFromStream(Stream);
for I := 0 to Cnt -1 do
begin
V := T.Create;
V.LoadFromStream(TStream(Stream));
Self.Add(V);
end;
end;
procedure TMyList<T>.SaveToStream(var Stream: TStream);
var
I: Integer;
begin
WriteIntegerToStream(Stream,Self.Count);
for I := 0 to Self.Count -1 do
T(Items[I]).SaveToStream(TStream(Stream));
end;
procedure TMyList<T>.LoadFromFile(const FileName: String);
var
FS: TFileStream;
begin
FS := TFileStream.Create(FileName, fmOpenRead);
try
FS.Position := 0;
LoadFromStream(FS);
finally
FS.Free;
end;
end;
procedure TMyList<T>.SaveToFile(const FileName: String);
var
FS: TFileStream;
begin
FS := TFileStream.Create(FileName, fmCreate);
try
FS.Position := 0;
SaveToStream(TStream(FS));
finally
FS.Free;
end;
end;
{ TMusic }
constructor TMusic.Create(Title, Artist: String);
begin
FTitle := Title;
FArtist := Artist;
end;
constructor TMusic.Create;
begin
FTitle := '';
FArtist := '';
end;
procedure TMusic.Assign(Source: TPersistent);
begin
if Assigned(Source) and (Source is TMusic) then
begin
Title := (Source as TMusic).Title;
Artist := (Source as TMusic).Artist;
Exit;
end;
inherited;
end;
procedure TMusic.LoadFromStream(Stream: TStream);
begin
FTitle := ReadStringFromStream(Stream);
FArtist := ReadStringFromStream(Stream);
end;
procedure TMusic.SaveToStream(var Stream: TStream);
begin
WriteStringToStream(Stream, FTitle);
WriteStringToStream(Stream, FArtist);
end;
次のように使います。
procedure TForm1.Button1Click(Sender: TObject);
const
FN = 'MusicList.dat';
var
MusicList: TMyList<TMusic>;
Music1: TMusic;
Music2: TMusic;
begin
MusicList:= TMyList<TMusic>.Create;
try
// 追加
MusicList.Add(TMusic.Create('love','John Lennon'));
MusicList.Add(TMusic.Create('yesterady','the Beatles'));
MusicList.Add(TMusic.Create('Born This Way','Lady Gaga'));
// 追加
Music1 := TMusic.Create;
Music1.Title := 'Burn';
Music1.Artist := 'Deep Purple';
MusicList.Add(Music1);
// 追加(Assignを利用)
Music2 := TMusic.Create;
Music2.Assign(Music1);
MusicList.Add(Music2);
// ファイルに保存します。
MusicList.SaveToFile(ExtractFilePath(Application.ExeName) + FN);
finally
FreeAndNil(MusicList);
end;
end;
// load
procedure TForm1.Button2Click(Sender: TObject);
const
FN = 'MusicList.dat';
var
Music: TMusic;
Musiclist: TMyList<TMusic>;
begin
Memo1.Lines.Clear;
MusicList:= TMyList<TMusic>.Create;
try
MusicList.LoadFromFile(ExtractFilePath(Application.ExeName) + FN);
for Music in Musiclist do
Memo1.Lines.Add(Music.Title+' / '+Music.Artist);
finally
MusicList.Free;
end;
end;
Streamの読み書きは、次の手続き・関数を使います。Unicodeの文字列は、ちょっと面倒です。
procedure WriteIntegerToStream(var Stream: TStream; Value: Integer);
begin
Stream.WriteBuffer(Value, SizeOf(Integer));
end;
function ReadIntegerFromStream(const Stream: TStream): Integer;
begin
Stream.ReadBuffer(Result, SizeOf(Integer));
end;
procedure WriteStringToStream(var Stream: TStream; Value: String);
var
I: Integer;
S: UTF8String;
begin
S := UTF8String(Value);
I := Length(S);
Stream.WriteBuffer(I, SizeOf(Integer));
if I > 0 then
Stream.WriteBuffer(Pointer(S)^, I);
end;
function ReadStringFromStream(const Stream: TStream): String;
var
I: Integer;
S: UTF8String;
begin
Stream.ReadBuffer(I, SizeOf(Integer));
if I > 0 then
begin
SetLength(S, I);
Stream.ReadBuffer(Pointer(S)^, I);
Result := String(S);
end;
end;
| 固定リンク
