1

Is it possible to serialize a TCollection which is not encapsulated in a TComponent ?

For example, I have a custom TCollection. I can't use TMemoryStream.WriteComponent() on my TCollection descendant. It'll only works if I encapsulate the collection in a TComponent and then if I write this component.

Technically there is no problem but declaring a TComponent which only owns a TCollection seems a bit odd.

TMyCustomCollection = Class(TCollection) // not serializable ?
  //...
End;

TMyCustomCollectionCapsule = Class(TComponent) // serializable !
Private
  FMyCusColl: TMyCustomCollection;
  Procedure SetMyCusColl(Const Data: TMyCustomCollection);
Published
  Property CanBeSerialized: TMyCustomCollection Read FMyCusColl Write SetMyCusColl
End;

Maybe I just miss a feature of the Delphi RTL? Can a TPersistent descendent be streamed without being itself encapsulated in a TComponent ?

menjaraz
  • 7,551
  • 4
  • 41
  • 81
az01
  • 1,988
  • 13
  • 27
  • 1
    if XML is an option, I would highly recommend OmniXML(http://www.omnixml.com/), I use it in many projects because of the simplicity, 1 call to load it, 1 call to save it, simple and efficient. –  Jan 14 '12 at 17:59

2 Answers2

1

It only works if you put your collection in a TComponent, because TMemoryStream.WriteComponent (the name itself is a clue!) takes a TComponent as a parameter:

procedure WriteComponent(Instance: TComponent);

and TCollection is as you already discovered not a TComponent descendant. It may seem odd to have a TComponent descendant just to hold your TCollection descendant, but if you want to stream it using the WriteComponent facilities of streams, I don't see any other easy way to do it.


If you want to do this using "just" the RTL/VCL (ie not using a third party library), you would have to write a T(Memory)Stream descendant and add a WritePersistent implementation that takes an Instance: TPersistent parameter.

I haven't delpheddelved into the TStream classes that much, but my guess is that you w/should be able to borrow a lot from the TComponent support. Certainly the class inheritance support.

Having had a cursory look, it seems simple at first as WriteComponent just calls WriteDescendent which instantiates a TWriter and then calls the WriteDescendent method of that writer. And the TWriter already contains methods to write a collection.

However, if you "just" want to stream TPersistent descendants, you will have to do a lot of work in TWriter/TReader as well as they are completely based around TComponent. And it won't be a simple case of just writing a couple of descendant. For one, they TWriter/TReader are not really set up to be derived from. For another: TStream (descendants) instantiate TWriter and TReader directly and these classes do not have virtual constructors. Which makes writing descendants for them fairly futile unless you would like to try your hand at hooking, patching the VMT and more of that interesting stuff.

All in all: the easiest way to stream your custom collection remains to "just" wrap it in a TComponent and live with the "waste" of that.

Marjan Venema
  • 19,136
  • 6
  • 65
  • 79
  • Yes, I've noticed this but my question was more related to 'How to'. someone has posted another answer which was suggesting to use a 3rd part library...but the peer has deleted the post...I thought it was possible in the RTL...However it's still easyer to use a capsule rather than dealing with the RTTI... – az01 Jan 14 '12 at 15:34
  • @az01: Yes, I got that, but the answer to the "How to" isn't that simple, certainly if you are wanting to use the RTL/VCL only. See update to my answer. – Marjan Venema Jan 14 '12 at 16:04
  • A question-asking tip for you, Az01: If you want to know *how* to do something, then don't ask questions that can be answered with a simple *yes* or *no*. Don't ask "Can I do X?" Ask "How do I do X?" If it turns out that you can't actually do X at all, then you'll get that answer anyway. If you start with a yes/no question, just *assume* the answer is the one you want and skip straight to the natural follow-up question instead. – Rob Kennedy Jan 14 '12 at 18:38
1

You can serialize a TCollection not encapsuled within a TComponent by means of another TComponent descendant defined as follows:

type
  TCollectionSerializer = class(TComponent)
  protected
    FCollectionData: string;
    procedure DefineProperties(Filer: TFiler); override;
  public
    procedure WriteData(Stream: TStream);
    procedure ReadData(Stream: TStream);
    //
    procedure LoadFromCollection(ACollection: TCollection);
    procedure SaveToCollection(ACollection: TCollection);
  end;

DefineProperties, WriteData and ReadData implementation details:

procedure TCollectionSerializer.WriteData(Stream: TStream);
var
  StrStream: TStringStream;
begin
  StrStream:=TStringStream.Create;
  try
    StrStream.WriteString(FCollectionData);
    Stream.CopyFrom(StrStream,0);
  finally
    StrStream.Free;
  end;
end;

procedure TCollectionSerializer.ReadData(Stream: TStream);
var
  StrStream: TStringStream;
begin
  StrStream:=TStringStream.Create;
  try
    StrStream.CopyFrom(Stream,0);
    FCollectionData:=StrStream.DataString;
  finally
    StrStream.Free;
  end;
end;

procedure TCollectionSerializer.DefineProperties(Filer: TFiler);
begin
  inherited;
  //
  Filer.DefineBinaryProperty('CollectionData', ReadData, WriteData,True);
end;

Templates of LoadFromCollection and SaveToCollection:

procedure TCollectionSerializer.LoadFromCollection(ACollection: TCollection);
var
  CollectionStream: TStream;
begin
  CollectionStream:= TCollectionStream.Create(ACollection);
  try
    ReadData(CollectionStream)
  finally
    CollectionStream.Free;
  end;
end;

procedure TCollectionSerializer.SaveToCollection(ACollection: TCollection);
var
  CollectionStream: TStream;
begin
  CollectionStream:= TCollectionStream.Create(ACollection);
  try
    WriteData(CollectionStream);
  finally
    CollectionStream.Free;
  end;
end;

About TCollectionStream:

It should be a TStream descendant having a rich creator with a TCollection as a parameter and designed to behave like a TFileStream. You must implement it. Disclaimer: I have never tested that but I can tell that a TFileStream works (for streaming external file).

Conclusion:

This component is inspired by the VCL way to serialize within a DFM an external file under Delphi XE (RCData). It must be registred along with a component editor (that you must also implement based on TComponentEditor) doing the serialization at designtime.

menjaraz
  • 7,551
  • 4
  • 41
  • 81