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;
| 固定リンク