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;
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With