Genericsの型キャスト?

今更、「Genericsかぁー」なんて突っ込みたくなるでしょうけど、Delphi2009をインストール後、ほとんどプログラムをしてこなかった私にとっては、悩みどころの一つです。

あるクラスとそれを管理するリストを考えた場合、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;

|