【问题标题】:Can I make a constructor that deserializes a string version of my object?我可以创建一个反序列化对象的字符串版本的构造函数吗?
【发布时间】:2011-01-18 16:11:01
【问题描述】:

我正在使用 Delphi 帮助文件中 ComponentToString 部分中的示例对对象(TComponent 后代)进行序列化和反序列化。这样我就可以将对象存储在数据库的 VARCHAR 字段中。

当我需要从存储在数据库中的字符串实例化我的类的新实例时,我可以使用CreateFromString(AOwner: TComponent; AData: String) 形式的构造函数来实现吗?还是我必须使用返回组件类实例的非类方法?

如果我可以使用构造函数版本,我如何将 ReadComponent 的返回值“映射”到构造函数正在创建的“自我”?

这是帮助文件中的反序列化示例:

function StringToComponentProc(Value: string): TComponent;
var
  StrStream:TStringStream;
  BinStream: TMemoryStream;
begin
  StrStream := TStringStream.Create(Value);
  try
    BinStream := TMemoryStream.Create;
    try
      ObjectTextToBinary(StrStream, BinStream);
      BinStream.Seek(0, soFromBeginning);
      Result:= BinStream.ReadComponent(nil);
    finally
      BinStream.Free;
    end;
  finally
    StrStream.Free;
  end;
end;

【问题讨论】:

    标签: delphi serialization delphi-2010


    【解决方案1】:

    一般来说,是的,您可以让构造函数反序列化字符串并使用该信息来初始化新实例。一个简单的例子是具有单个 Integer 字段的类。将字符串传递给构造函数并让构造函数调用StrToInt 并使用结果初始化字段。

    但是,如果您用于反序列化的唯一函数是一个也创建实例的函数,那么您就不能从构造函数中使用它,因为当您只需要一个时,您最终会得到两个实例。构造函数没有办法说:“没关系;毕竟不要构造实例。我已经在其他地方得到了一个。”

    但是,您所处的情况并非如此。您应该知道,TStream.ReadComponent 允许您自己创建实例。如果您还没有给它一个实例来使用它,它只会实例化该类。你应该可以这样写你的构造函数:

    constructor TLarryComponent.CreateFromString(const AData: string);
    var
      StrStream, BinStream: TStream;
    begin
      Create(nil);
      StrStream := TStringStream.Create(AData);
      try
        BinStream := TMemoryStream.Create;
        try
          ObjectTextToBinary(StrStream, BinStream);
          BinStream.Position := 0;
          BinStream.ReadComponent(Self);
        finally
          BinStream.Free;
        end;
      finally
        StrStream.Free;
      end;
    end;
    

    我们将Self 指定的当前对象传递给ReadComponent。流将忽略存储在流中的类名,并假定当前对象属于正确的类。

    【讨论】:

    • 您的实现中是否可能存在小错误?我觉得应该是Create(nil);不是inherited Create(nil);。使用“继承”,您将错过创建 TLarryComponent 引入的任何字段存储和属性。
    • 你是对的。我在想ReadComponent 会解决这个问题,但我现在意识到它不可能。
    • 现在可以完美运行并为我的问题提供优雅的解决方案。
    【解决方案2】:

    您可以通过class(静态)方法执行此操作,但不能通过constructor
    Delphis 的构造函数由刚刚创建的实例的内部编译器调用,该实例已经部分初始化(它是所需类的,并且实例/字段存储被清零)。

    如果你看到TStream.ReadComponent的源码,你会发现组件的真实类首先是从源流中读取的,然后RTTI从流中构造并填充一个空实例并作为结果返回.这意味着:

    要使用TStream.ReadComponent,您需要通过RegisterClass 将您的课程注册到Delphis 的流媒体系统。

    【讨论】:

    • 谢谢维克多。这个答案包含一个关于 RegisterClass 的重要说明,如果您没有想到要指出它,可能会让我挠头一个小时。
    【解决方案3】:

    使用静态 class function 而不是 constructor

    type
      TYourClass = class(TComponent)
      public
        class function CreateFromString(AOwner: TComponent; AData: String): TYourClass; static;
      end;
    
    implementation
    
    class function TYourClass.CreateFromString(AOwner: TComponent; AData: String): TYourClass;
    begin
       Result := (StringToComponentProc(AData) as TYourClass);
       if AOwner <> nil then
         AOwner.InsertComponent(Result);
    end;
    

    但 AOWner 部分可能是个问题,因为 TStream.ReadComponent 没有所有者参数。

    关于这个问题还有另外一个问题:

    How can I specify the Owner of component read from a Delphi TStream?

    编辑:我已经更新了代码示例以包含所有者。

    请注意,插入到所有者的组件列表中需要一个唯一的或空的Name 用于正在插入的组件。

    【讨论】:

    • 其实机主问题不是问题。我将其设为 TComponent 后代只是为了获得对自动流式传输功能的访问权限。事实上,对象没有可见的表示,我不会在设计时使用它,我将在代码中管理它的生命周期。所以我可以让所有者为零。
    猜你喜欢
    • 2010-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-05
    • 2020-11-28
    • 2012-03-03
    相关资源
    最近更新 更多