【问题标题】:Serializing TObjectList<T> to stream将 TObjectList<T> 序列化为流式传输
【发布时间】:2012-12-19 17:00:14
【问题描述】:

我想知道如何执行通用TObjectList&lt;T&gt; 容器的序列化。基本上,我想在该列表中存储不同的对象,但所有对象都将来自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() 方法来将每个新类添加到字典中,我不太喜欢这种方式。

还有其他方法吗,或者我应该按照我建议的方式去做吗?

非常感谢。

【问题讨论】:

  • 当然可以。你必须(再次)解决之前已经解决了很多次的所有问题。
  • 我没有更多建议了。使用 JSON 或 XML。并使用众多序列化库之一。不要重新发明轮子。通常,当您这样做时,您的车轮会出现拐角。
  • 我和@DavidHeffernan 在同一页上,如果你想要一些已经制作好的东西,omnixml 是一个很好的选择,现在使用它很多年了code.google.com/p/omnixml
  • 您可以使用扩展 RTTI 序列化任何对象。无需从 TSerializable 类继承..
  • @whosrdaddy 可能会导致更快的加载/保存或以正确的顺序排序属性。有些人认为 RTTI 是在重新发明轮子。

标签: delphi generics serialization


【解决方案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 版本中编译。此外,您可能需要在 DPR 文件中添加 {$STRONGLINKTYPES ON} 或发明一些其他机制,以便链接器不会跳过您的类(David Heffernan 建议一种方法 here

【讨论】:

  • 我强烈建议将 SizeOf(Integer|Char) 替换为常量,并在某处添加警告或注释“Integer”是 32 位,如果您在 64 位中编译它,您会有惊喜。
  • @Computer in Delphi 64 bits,Integer 类型还是 32 bits,所以我觉得没什么意外。
  • @jachguate 我没有可以针对 64 位的 delphi,所以,NativeInt 是 64 位的?
  • 根据官方文档,Integer 在 x86 和 x64 上始终为 32 位,而NativeInt 在 x86 上为 32 位,在 x64 上为 64 位。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-30
  • 1970-01-01
  • 1970-01-01
  • 2017-08-18
  • 1970-01-01
  • 2020-07-17
  • 1970-01-01
相关资源
最近更新 更多