I've the following code that deals with TStream
writing and reading, but my problem (issue) is reading/writing TILObjects
using the TListHelper
class. Please read the comments inside these methods:
procedure TListHelper<T>.Serialize(const Buffer: IBuffer; const List: IList<T>);
function TListHelper<T>.Deserialize(const Buffer: IBuffer): IList<T>;
type
IBuffer = interface
procedure WriteInteger(Value: Integer);
function ReadInteger:Integer
end;
TBuffer = class(TInterfacedObject, IBuffer)
private
FStream: TStream;
public
constructor Create(AStream: TStream);
procedure WriteInteger(Value: Integer);
function ReadInteger: Integer;
end;
type
ILObject = interface
['{D4E3A925-58F2-4E8C-93E7-B49C1C3B453D}']
procedure Serialize(Stream: IBuffer);
function Deserialize(Stream: IBuffer): ILObject;
end;
TILObject = class(TInterfacedObject, ILObject)
public
procedure Serialize(Stream: IBuffer); virtual; abstract;
function Deserialize(Stream: IBuffer): ILObject; virtual; abstract;
end;
type
IListHelper<T> = interface
procedure Serialize(const Buffer: IBuffer; const List: IList<T>);
function Deserialize(const Buffer: IBuffer): IList<T>;
end;
TListHelper<T> = class(TInterfacedObject, IListHelper<T>)
public
procedure Serialize(const Buffer: IBuffer; const List: IList<T>);
function Deserialize(const Buffer: IBuffer): IList<T>;
end;
// test classes
TChild = class(TILObject)
private
public
constructor Create; overload; override;
destructor Destroy; override;
procedure Serialize(Stream: IBuffer); override;
class function Deserialize(const Stream: IBuffer): TChild; reintroduce; overload;
end;
TFather = class(TILObject)
private
FChildList: IList<TChild>;
procedure ReadChild(const Stream: IBuffer);
public
constructor Create; overload; override;
destructor Destroy; override;
procedure Serialize(Stream: IBuffer); override;
class function Deserialize(const Stream: IBuffer): ILObject; reintroduce; overload;
property ChildList: IList<TChild> read FChildList write FChildList;
end;
//
implementation
constructor TBuffer.Create(AStream: TStream);
begin
inherited Create;
FStream := AStream;
end;
procedure TBuffer.WriteInteger(Value: Integer);
begin
FStream.WriteBuffer(Value, SizeOf(Value));
end;
function TBuffer.ReadInteger: Integer;
begin
FStream.ReadBuffer(Result, SizeOf(Result));
end;
procedure TListHelper<T>.Serialize(const Buffer: IBuffer; const List: IList<T>);
var
Count, i: Integer;
begin
Count := List.Count;
Buffer.WriteInteger(Count);
for i := 0 to Count - 1 do
begin
if TypeInfo(T) = TypeInfo(Integer) then
Buffer.WriteInteger(Integer(TValue.From<T>(List[I]).AsType<Integer>))
// here how deal with the TILObjects.Serialize
// I want to have TILObject.Serialize(Buffer)
// else if TILObject
else
raise Exception.Create('Unsupported type for List serialization');
end;
end;
function TListHelper<T>.Deserialize(const Buffer: IBuffer): IList<T>;
var
Count, i: Integer;
Item: TValue;
LObject: TILObject;
begin
Result := TList<T>.Create;
Count := Buffer.ReadInt32;
for i := 0 to Count - 1 do
begin
if TypeInfo(T) = TypeInfo(Integer) then
Item := TValue.From<Integer>(Buffer.ReadInteger)
// here how to deal with TILObject.Deserialize
// else if TILObject.Deserialize(Buffer)
else
raise Exception.Create('Unsupported type for List deserialization');
Result.Add(Item.AsType<T>);
end;
end;
// test classes
constructor TChild.Create;
begin
inherited Create;
end;
destructor TChild.Destroy;
begin
inherited Destroy;
end;
procedure TChild.Serialize(Stream: IBuffer);
begin
inherited Serialize(Stream);
end;
constructor TFather.Create;
begin
inherited Create;
FChildList := TList<TChild>.Create;
end;
class function TChild.Deserialize(const Stream: IBuffer): ILObject;
begin
Result := TChild.Create;
end;
How to use TIlObject.Serialize()
inside TListHelper<T>.Serialize()
and TILObject.Deserialize()
inside TListHelper<T>.Deserialize()
?
Testing the return value of TypeInfo(T)
alone won't help you in this situation, as you would have to test for specific types. Instead, you can use GetTypeKind(T)
(XE7+), checking for tkInteger
, tkInterface
, and tkClass
types, and handle their data accordingly.
For example:
procedure TListHelper<T>.Serialize(const Buffer: IBuffer; const List: IList<T>);
var
Count, i: Integer;
LIntf: ILObject;
LObject: TObject;
begin
Count := List.Count;
Buffer.WriteInteger(Count);
for i := 0 to Count - 1 do
begin
case GetTypeKind(T) of
tkInteger: begin
Buffer.WriteInteger(TValue.From<T>(List[I]).AsType<Integer>);
end;
tkInterface: begin
if Supports(TValue.From<T>(List[I]).AsInterface, ILObject, LIntf) then
LIntf.Serialize(Buffer)
else
raise Exception.Create('Unsupported interface type for List serialization');
end;
tkClass: begin
LObject := TValue.From<T>(List[I]).AsObject;
if LObject.InheritsFrom(TILObject) then
TILObject(LObject).Serialize(Buffer)
else
raise Exception.Create('Unsupported object type for List serialization');
end;
else
raise Exception.Create('Unsupported type for List serialization');
end;
end;
end;
function TListHelper<T>.Deserialize(const Buffer: IBuffer): IList<T>;
var
Count, i: Integer;
Item: TValue;
LIntf: ILObjct;
LObject: TILObject;
begin
Result := TList<T>.Create;
Count := Buffer.ReadInt32;
for i := 0 to Count - 1 do
begin
case GetTypeKind(T) of
tkInteger: begin
Item := TValue.From<T>(Buffer.ReadInteger);
end;
tkInterface: begin
LIntf := ???.Create as ILObject;
LIntf.Deserialize(Buffer);
Item := TValue.From<T>(LIntf);
end;
tkClass: begin
LObject := ???.Create as TILObject;
LObject.Deserialize(Buffer);
Item := TValue.From<T>(LObject);
end;
else
raise Exception.Create('Unsupported type for List deserialization');
end;
Result.Add(Item.AsType<T>);
end;
end;
However, as you can see above, there is a problem with TListHelper.Deserialize()
. You have to create an object instance first in order to call (T)ILObject.Deserialize()
on it, so you need to know which class type to create. But you can't call T.Create
since T
doesn't have the class
constraint. So you will have to write the class name into your Buffer
, read it back, and use a separate factory to create and return a new (T)ILObject
instance for the specified class name, eg:
procedure TListHelper<T>.Serialize(const Buffer: IBuffer; const List: IList<T>);
var
Count, i: Integer;
LIntf: ILObject;
LObject: TObject;
begin
Count := List.Count;
Buffer.WriteInteger(Count);
for i := 0 to Count - 1 do
begin
case GetTypeKind(T) of
tkInteger: begin
Buffer.WriteInteger(TValue.From<T>(List[I]).AsType<Integer>);
end;
tkInterface: begin
if Supports(TValue.From<T>(List[I]).AsInterface, ILObject, LIntf) then
begin
Buffer.WriteString((LIntf as TObject).ClassName);
LIntf.Serialize(Buffer);
end else
raise Exception.Create('Unsupported interface type for List serialization');
end;
tkClass: begin
LObject := TValue.From<T>(List[I]).AsObject;
if LObject.InheritsFrom(TILObject) then
begin
Buffer.WriteString(LObject.ClassName);
TILObject(LObject).Serialize(Buffer)
end else
raise Exception.Create('Unsupported object type for List serialization');
end;
else
raise Exception.Create('Unsupported type for List serialization');
end;
end;
end;
function TListHelper<T>.Deserialize(const Buffer: IBuffer): IList<T>;
var
Count, i: Integer;
Item: TValue;
LClassName: string;
LIntf: ILObjct;
LObject: TILObject;
begin
Result := TList<T>.Create;
Count := Buffer.ReadInt32;
for i := 0 to Count - 1 do
begin
case GetTypeKind(T) of
tkInteger: begin
Item := TValue.From<T>(Buffer.ReadInteger);
end;
tkInterface: begin
LClassName := Buffer.ReadString;
LIntf := CreateILObject(LClassName) as ILObject; // <--
LIntf.Deserialize(Buffer);
Item := TValue.From<T>(LIntf);
end;
tkClass: begin
LClassName := Buffer.ReadString;
LObject := CreateILObject(LClassName); // <--
LObject.Deserialize(Buffer);
Item := TValue.From<T>(LObject);
end;
else
raise Exception.Create('Unsupported type for List deserialization');
end;
Result.Add(Item.AsType<T>);
end;
end;
You will have to implement CreateILObject()
yourself, eg:
function CreateILObject(AClassName: string): TILObject;
begin
if AClassName = 'TChild' then
Result := TChild.Create
...
else
Result := nil; // or raise
end;
There are ways to generalize such a factory, but that is outside the scope of this question. There are many other questions on StackOverflow (and other forums) related to (de)serializing objects.