Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
like image 672
Larry Lustig Avatar asked Jan 18 '11 16:01

Larry Lustig


2 Answers

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.

like image 102
Rob Kennedy Avatar answered Sep 28 '22 01:09

Rob Kennedy


You can do this by a class (static) method, but not via a constructor.
Delphis' constructors are called by compiler intrinsic on the just-created instance, which is already partially initialized (it's of the desired class and instance/field storage is zeroed-out).

If you see the source of TStream.ReadComponent, you'll find that the components' real class is read from the source stream at first, then an empty instance is constructed and filled by RTTI from the stream and returned as the result. Which means:

To use TStream.ReadComponent, you'll need to register your class to Delphis' streaming system via RegisterClass.

like image 24
Viktor Svub Avatar answered Sep 28 '22 01:09

Viktor Svub