【问题标题】:Delphi: Saving an array of objects to a file. (Serialization)Delphi:将对象数组保存到文件中。 (序列化)
【发布时间】:2017-03-24 14:11:15
【问题描述】:

我正在构建一个 3D 应用程序。我的表单上有一个 3D 布局(TLayout3D),我在运行时创建和定位 TSphere。 Spheres 是我创建的自定义 TNode 类的一部分:

TNode = Class;

Sphere :TSphere;
ID :String;
NodeType :string;
TotalDistance :integer; //used in Dijkstras algorithm

End;

我有一个由 TNode 类组成的数组

NodesArray : array [1..100] of TNode;

我需要知道如何保存数组,然后从文件中加载它,这样我以后就不必“手动”再次创建它。

感谢您的帮助,谢谢。

【问题讨论】:

  • 一种方法是去掉数组并使用 TCollection 和 TCollectionItem 的后代。有了这些,以及使用已发布的属性,您几乎可以免费获得流式传输和持久性 :) 这就是 IDE 用来处理可视组件的类数组属性的方法。
  • 加载不同的方法来做到这一点。你想怎么做?您首选的方法是什么?使用固定长度的数组不是一个好主意。当您需要超过 100 个元素时会发生什么?使用集合或动态数组,或TList<T>
  • @Frazz 感谢您的回复。如果问的不多,你能给我一个你方法的代码示例吗?
  • @DavidHeffernan 我可以使用动态数组,然后再设置长度。我不知道你的首选方法是什么意思。你会推荐什么?我只需要保存它,然后在关闭程序后加载它。感谢您的回复。
  • 一个明显的选择是使用 JSON。有很多帖子描述了如何做到这一点。

标签: delphi serialization file-handling


【解决方案1】:

虽然我同意 David 的观点,现在 JSON 将是持久化业务对象和容器的有效选择,但我想在此处发布使用 TCollection 和 TCollectionItem 执行此操作的代码。这是 Delphi 一直以来使用的旧方法,将组件属性流式传输到 dfm 文件。

但这里有一个警告。我已经测试过这段代码,但它不起作用……不是因为它坏了(我多年来一直使用类似的代码来持久化业务对象),而是因为 TSphere 不支持 TPersistent Assign 和 AssignTo 接口。为此,集合项的属性必须是简单的数据类型和记录,或者是适当的 TPersistent 实现。而 TSphere 不是这些。

不管怎样,代码如下:

界面

Type
  TNode = Class(TCollectionItem)
  Private
    FSphere       : TSphere;
    FID           : String;
    FNodeType     : String;
    FTotalDistance: integer;
    Procedure SetSphere(Const Value: TSphere);
  Public
    Constructor Create(Collection: TCollection); Override;
    Destructor Destroy; Override;
    Procedure Assign(Source: TPersistent); Override;
  Published
    Property Sphere       : TSphere Read FSphere Write SetSphere;
    Property ID           : String Read FID Write FID;
    Property NodeType     : String Read FNodeType Write FNodeType;
    Property TotalDistance: integer Read FTotalDistance Write FTotalDistance; // used in Dijkstras algorithm
  End;

Type
  TNodes = Class(TCollection)
  Private
    Function GetItem(Index: integer): TNode;
    Procedure SetItem(Index: integer; Value: TNode);
  Public
    Constructor Create; Reintroduce;
    Function Add: TNode;
    Procedure LoadFromFile(Const Filename: String);
    Procedure LoadFromStream(S: TStream);
    Procedure SaveToFile(Const Filename: String);
    Procedure SaveToStream(S: TStream);
    Property Items[Index: integer]: TNode Read GetItem Write SetItem; Default;
  End;

Type
  TNodesWrapper = Class(TComponent)
  Private
    FCollection: TNodes;
  Public
    Constructor Create(AOwner: TComponent); Override;
    Destructor Destroy; Override;
  Published
    Property Collection: TNodes Read FCollection Write FCollection;
  End;

实施

{ TNode }

Procedure TNode.Assign(Source: TPersistent);
Begin
  If Source Is TNode Then Begin
    If Assigned(Collection) Then
      Collection.BeginUpdate;
    Try
      Sphere        := TNode(Source).Sphere;
      ID            := TNode(Source).ID;
      NodeType      := TNode(Source).NodeType;
      TotalDistance := TNode(Source).TotalDistance;
    Finally
      If Assigned(Collection) Then
        Collection.EndUpdate;
    End;
  End
  Else
    Inherited;
End;

Constructor TNode.Create(Collection: TCollection);
Begin
  Inherited;
  FSphere := TSphere.Create(Nil);
  // Set default values here
End;

Destructor TNode.Destroy;
Begin
  FreeAndNil(FSphere);
  Inherited;
End;

Procedure TNode.SetSphere(Const Value: TSphere);
Begin
  FSphere.Assign(Value);
End;

{ TNodes }

Function TNodes.Add: TNode;
Begin
  Result := TNode(Inherited Add);
End;

Constructor TNodes.Create;
Begin
  Inherited Create(TNode);
End;

Function TNodes.GetItem(Index: integer): TNode;
Begin
  Result := TNode(Inherited GetItem(Index));
End;

Procedure TNodes.LoadFromFile(Const Filename: String);
Var
  S: TFileStream;
Begin
  S := TFileStream.Create(Filename, fmOpenRead);
  Try
    LoadFromStream(S);
  Finally
    S.Free;
  End;
End;

Procedure TNodes.LoadFromStream(S: TStream);
Var
  Wrapper: TNodesWrapper;
  SBin   : TMemoryStream;
Begin
  SBin    := TMemoryStream.Create;
  Wrapper := TNodesWrapper.Create(Nil);
  Try
    ObjectTextToBinary(S, SBin);
    SBin.Position := 0;
    SBin.ReadComponent(Wrapper);
    Assign(Wrapper.Collection);
  Finally
    Wrapper.Free;
    SBin.Free;
  End;
End;

Procedure TNodes.SaveToFile(Const Filename: String);
Var
  S: TStream;
Begin
  S := TFileStream.Create(Filename, fmCreate);
  Try
    SaveToStream(S);
  Finally
    S.Free;
  End;
End;

Procedure TNodes.SaveToStream(S: TStream);
Var
  Wrapper: TNodesWrapper;
  SBin   : TMemoryStream;
Begin
  SBin    := TMemoryStream.Create;
  Wrapper := TNodesWrapper.Create(Nil);
  Try
    Wrapper.Collection.Assign(Self);
    SBin.WriteComponent(Wrapper);
    SBin.Position := 0;
    ObjectBinaryToText(SBin, S);
  Finally
    Wrapper.Free;
    SBin.Free;
  End;
End;

Procedure TNodes.SetItem(Index: integer; Value: TNode);
Begin
  Inherited SetItem(Index, Value);
End;

{ TNodesWrapper }

Constructor TNodesWrapper.Create(AOwner: TComponent);
Begin
  Inherited;
  FCollection := TNodes.Create;
End;

Destructor TNodesWrapper.Destroy;
Begin
  FreeAndNil(FCollection);
  Inherited;
End;

TCollection 仍在使用中,尽管其中一些代码对于习惯使用通用容器的人来说可能看起来很奇怪...其中大部分与类型转换和将一个 TCollectionItem 类链接到其特定的 TCollection 类有关。

奇迹发生在 Stream ReadComponent 和 WriteComponent 方法中。不幸的是,由于 TCollection 不是 TComponent,它必须包装在 TComponent 中......这就是 TNodesWrapper 的用途。

它可能看起来很奇怪和复杂,但其中大部分可以抽象为通用 TCollection 后代,它添加了加载和保存到文件/流/字符串的能力。所以大部分代码都可以隐藏在引擎盖下。

而且...我再说一遍...这仅适用于您想要持久的属性本身是可持久的...而 TSphere 不是。所以我现在想知道 FireMonkey 3D 表单是如何保持它们的,因为我还没有真正开发 3D 应用程序。

【讨论】:

    【解决方案2】:

    根本不要使用数组和静态大小/绑定的东西!应用一项责任规则。为您的实体创建序列化程序。在这种情况下,您可以改变主意并为任何格式创建任意数量的序列化程序。您的代码变得更加灵活、可变和可测试。

    TSphere = class
      // Entity. Just fields with getter/setter methods
    end;
    
    TContext = TDictionary<string><TObject>;
    
    CONST_ctxkey_Factory = 'factory';
    
    IStream = interface (IInvokable )
      procedure load( var data_; size_ : cardinal );
      procedure store( var data_; size_ : cardinal );
    end;
    
    ISerializer = interface ( TInvokable )
      procedure load( ctx_ : TContext );
      procedure store( ctx_ : TContext );
    end;
    
    TSerializer = class ( TInterfaceObject, ISerializer )
      protected
        // Attributes
        fObject : TObject;
        fStream : IStream;
    
      public
        constructor create( object_ : TObject; stream_ : IStream );
    
        // Realized methods (ISerializer)
        procedure load( ctx_ : TContext ); virtual; abstract;
        procedure store( ctx_ : TContext ); virtual; abstract;        
    end;
    
    TSphereSerializer_XML = class ( TSerializer )
      public
        // Overriden methods
        procedure load( ctx_ : TContext ); override;
        procedure store( ctx_ : TContext ); override;      
    end;
    
    TSpheresMainSerializer_BIN = class ( TSerializer )
      public
        // Overriden methods
        procedure load( ctx_ : TContext ); override;
        procedure store( ctx_ : TContext ); override;
    end;
    
    TSpheresMainSerializer_BIN.store( ctx_ : TContext );
    var
      spheres : TSphereList;
      sf : TSerializerContext;
      sph : TSphere;
      iSe : ISerializer;
    begin
      spheres := TSphereList( fObject );
      sf := ctx_.items[CONST_ctxkey_Factory];
      fStream.write( version_number, sizeOf( cardinal ) );
      fStream.write( spheres.count, sizeOf( cardinal ) );
      for sph in shperes_ do
      begin
        iSe := sf.createShpereSerializer( sph );
        iSe.store( ctx );
      end;
    end;
    
    TSphereSerializer_BIN = class ( TSerializer )
      public
        // Overriden methods
        procedure load( ctx_ : TContext ); override;
        procedure store( ctx_ : TContext ); override;
    end;
    
    TSphereList = TList<TSphere>;
    
    TSerializerFactory = class
      public
        // It creates serializers for XML format
        function createContext : TContext; virtual;
        function createSpheresMainSerializer( spheres_ : TSphereList; stream_ : IStream ) : ISerializer; virtual; abstract;
        function createSphereSerializer( sphere_ : TSphere; stream_ : IStream ) : ISerializer; virtual; abstract;
    end;
    
    TSerializerFactory_BIN = class ( TSerializerFactory )
      public
        // It creates serializers for binary format
        function createSpheresMainSerializer( spheres_ : TSphereList; stream_ : IStream ) : ISerializer; override;
        function createSphereSerializer( sphere_ : TSphere; stream_ : IStream ) : ISerializer; override;
    end;
    
    function TSerializerFactory_BIN.createSpheresMainSerializer( spheres_ : TSphereList; stream_ : IStream ) : ISerializer; 
    begin
      result := TSpheresMainSerializer_BIN.create( TObject( spheres_ ), stream_ );
    end;
    
    function TSerializerFactory_BIN.createSphereSerializer( sphere_ : TSphere; stream_ : IStream ) : ISerializer; 
    begin
      result := TSphereSerializer_BIN.create( sphere_, stream_ );
    end;
    

    下一个过程的输出文件格式取决于作为参数(sf_)传递的TSerializerFactory:

    procedure saveSpheresToStream( spheres_ : TSphereList; stream_ : IStream; sf_ : TSerializerFactory );
    var
      ctx : TContext;
      iSe : ISerializer;
    begin
      try
        ctx := sf_.createContext;
        ctx.add( CONST_ctxkey_Factory, sf_ );
        try
          iSe := sf_.createSpheresMainSerializer( spheres_, stream_ );
          iSe.store( ctx );
        finally
          ctx.free;
        end;
    end;
    

    我知道它太厚了,乍一看还是太长了……但也许有用! :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-06
      • 1970-01-01
      • 2013-01-20
      • 2017-10-02
      相关资源
      最近更新 更多