0

TObjectList<T>汎用コンテナのシリアル化をどのように実行できるのか疑問に思っています。基本的に、そのリストにさまざまなオブジェクトを格納したいのですが、すべてのオブジェクトはTSerializable、次のように定義されているから派生します。

  TSerializable = class abstract(TObject)
  public
    { Public declarations }
    procedure LoadFromStream(const S: TStream); virtual; abstract;
    procedure SaveToStream(const S: TStream); virtual; abstract;
  end;

ここで、これらのクラスをアプリのどこかに定義したとしましょう。

type
    TExampleClass = class(TSerializable)
    private
        { Private declarations }
        FIntProp: Integer;
    public
        { Public declarations }
        constructor Create();

        procedure LoadFromStream(const S: TStream); override;
        procedure SaveToStream(const S: TStream); override;

        property IntProp: Integer read FIntProp write FIntProp;
    end;

    TAnotherExample = class(TSerializable)
    private
        { Private declarations }
        FStringProp: String;
    public
        { Public declarations }
        constructor Create();

        procedure LoadFromStream(const S: TStream); override;
        procedure SaveToStream(const S: TStream); override;

        procedure ReverseStringProp();

        property StringProp: String read FStringProp write FStringProp;
    end;

このようなオブジェクトをリストに保存することを計画しています。

var
    MS: TMemoryStream;
    SomeList: TObjectList<TSerializable>;
begin
    MS := TMemoryStream.Create();
    SomeList := TObjectList<TSerializable>.Create(True);
    try
        SomeList.Add(TExampleClass.Create());
        SomeList.Add(TAnotherClass.Create());

        TExampleClass(SomeList[0]).IntProp := 1992;
        TAnotherClass(SomeList[1]).StringProp := 'Some value';

        //  Here, a method to serialize the list...
        SerializeList(SomeList, MS);

        //  Clear the list and reset position in the stream.
        SomeList.Clear();
        MS.Seek(0, soFromBeginning);

        //  Unserialize the list.
        UnserializeList(SomeList, MS);

        //  Should display "Some value".
        Writeln(TAnotherClass(SomeList[1]).StringProp);
    finally
        SomeList.Free();
        MS.Free();
    end;
end;

では、リスト全体をシリアル化してストリーム化し、そのストリームからリストを再作成するにはどうすればよいでしょうか。

私が考えていたのは:

  1. リストを繰り返します。
  2. 最初に、各オブジェクトのクラス名をストリームに書き込みます。
  3. SaveToStream()そのオブジェクトを呼び出します。

しかし、そのアプローチを機能させるには、ある種のクラスレジスタを作成する必要があります。これは、既知のクラスを格納するためのある種の辞書になります。それは良い考えのように聞こえますが、それから私はRegisterClass()すべての新しいクラスを辞書に追加するためにいくつかのメソッドを呼び出す必要があります、そして私はその方法があまり好きではありません。

他の方法はありますか、それとも私が提案した方法でそれを行うべきですか?

本当にありがとう。

4

1 に答える 1

1

ヒントをありがとう。私は自分自身のアプローチを使用することにしました。これはおそらく最良のアプローチではありませんが、私の小さなプロジェクトのニーズに合っています。

誰かがそのようなアプローチに興味を持っているのではないかと思ったので、ここに投稿しました。

基本的に、私が決めたのは基本クラスを持つことですTSerializable

type
  TSerializable = class abstract(TObject)
  public
    { Public declarations }
    procedure LoadFromStream(const S: TStream); virtual; abstract;
    procedure SaveToStream(const S: TStream); virtual; abstract;
  end;

すべての子孫クラスは、個別にストリーミングするために保存を実装LoadFromStream()および処理する必要があります。SaveToStream()すべてのクラスプロパティを自動的にロード/保存するいくつかのジェネリックメソッドを作成するとよいでしょう。

次に、この小さなクラスがあります。

type
  TSerializableList = class(TObjectList<TSerializable>)
  public
    procedure Serialize(const S: TStream);
    procedure UnSerialize(const S: TStream);
  end;

コードは次のとおりです。

{ TSerializableList }

procedure TSerializableList.Serialize(const S: TStream);
var
  CurrentObj: TSerializable;
  StrLen, StrSize: Integer;
  ClsName: String;
begin
  S.Write(Self.Count, SizeOf(Integer));
  for CurrentObj in Self do
  begin
    ClsName := CurrentObj.QualifiedClassName();
    StrLen := Length(ClsName);
    StrSize := SizeOf(Char) * StrLen;

    S.Write(StrLen, SizeOf(Integer));
    S.Write(StrSize, SizeOf(Integer));
    S.Write(ClsName[1], StrSize);

    CurrentObj.SaveToStream(S);
  end;
end;

procedure TSerializableList.UnSerialize(const S: TStream);
var
  I, NewIdx, TotalCount, Tmp, Tmp2: Integer;
  ClsName: String;
  Context: TRttiContext;
  RttiType: TRttiInstanceType;
begin
  Context := TRttiContext.Create();
  try
    S.Read(TotalCount, SizeOf(Integer));
    for I := 0 to TotalCount -1 do
    begin
      S.Read(Tmp, SizeOf(Integer));
      S.Read(Tmp2, SizeOf(Integer));

      SetLength(ClsName, Tmp);
      S.Read(ClsName[1], Tmp2);

      RttiType := (Context.FindType(ClsName) as TRttiInstanceType);
      if (RttiType <> nil) then
      begin
        NewIdx := Self.Add(TSerializable(RttiType.MetaclassType.Create()));
        Self[NewIdx].LoadFromStream(S);
      end;
    end;
  finally
    Context.Free();
  end;
end;

速くて汚いですが、私が必要とするもののために働きます。

コードは拡張RTTIを使用しているため、古いDelphiバージョンではコンパイルされません。{$STRONGLINKTYPES ON}また、リンカーがクラスをスキップしないように、DPRファイルを追加するか、他のメカニズムを考案する必要がある場合があります(David Heffernanがここで1つの方法を提案します)

于 2012-12-19T20:35:49.650 に答える