Search code examples
delphiserializationdelphi-2010

Can I make a constructor that deserializes a string version of my object?


I'm serializing and deserializing an object (TComponent descendant) using the example in the ComponentToString section in the Delphi help file. This is so I can store the object in a VARCHAR field in the database.

When I need to instantiate a new instance of my class from a string stored in the database, can I do that using a constructor of the form CreateFromString(AOwner: TComponent; AData: String)? Or do I have to use a non-class method that returns an instance of my component class?

If I can use the constructor version, how to I "map" the return value of ReadComponent to the "self" that is being created by the constructor?

Here's the deserialization example from the help file:

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;

Solution

  • In general, yes, you can make a constructor deserialize a string and use that information to initialize the new instance. A trivial example of that would be a class with a single Integer field. Pass a string to the constructor and have the constructor call StrToInt and initialize the field with the result.

    But if the only function you have for deserialization is one that also creates the instance, then you cannot use that from the constructor because then you'll end up with two instances when you only wanted one. There's no way for a constructor to say, "Never mind; don't construct an instance after all. I already got one somewhere else."

    However, that's not the situation you're in. As you should know, TStream.ReadComponent allows you to create the instance yourself. It only instantiates the class if you haven't already given it an instance to use. You should be able to write your constructor like this:

    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;
    

    There we're passing the current object, designated by Self, to ReadComponent. The stream will ignore the class name stored in the stream and assume that the current object is of the correct class.