У меня есть следующий код, который занимается TStream
записью и чтением, но моя проблема (проблема) заключается в чтении/записи TILObjects
с использованием класса TListHelper
. Пожалуйста, прочитайте комментарии внутри этих методов:
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;
Как использовать TIlObject.Serialize()
внутри TListHelper<T>.Serialize()
и TILObject.Deserialize()
внутри TListHelper<T>.Deserialize()
?
@RemyLebeau, спасибо: if TypeInfo(T) = TypeInfo(Integer)
как использовать то же самое с ILObject
? Это правильно if TypeInfo(T) = TypeInfo(TILObject)
?
Не используйте TypeInfo(T) = TypeInfo(Integer)
, вместо этого используйте GetTypeKind(T) = tkInteger
. Затем вы можете проверить типы tkInterface
и tkClass
. Во-вторых, вы также можете создать TValue
безоговорочно, а затем использовать его свойство Kind
.
Однако у вас могут возникнуть проблемы с вашим TListHelper.Deserialize()
. Вам нужно создать экземпляр объекта для вызова (T)ILObject.Deserialize()
, поэтому вам нужно знать, какой тип класса создавать. Вы не можете вызвать T.Create
, поскольку T
не имеет ограничения class
. Таким образом, вам придется записать имя класса в свой Buffer
(просто), а затем прочитать его обратно (просто), а затем использовать отдельную фабрику для создания и возврата экземпляра ILObject
/TILObject
из указанного имени класса (большая работа).
@RemyLebeau большое спасибо; более подробную информацию (ЕСЛИ ВОЗМОЖНО, ПОЖАЛУЙСТА) на: «o вам придется записать имя класса в ваш буфер (легко), а затем прочитать его обратно (легко), а затем использовать отдельную фабрику для создания и возврата экземпляра ILObject/TILObject из указанное имя класса (много работы)"
Я опубликовал ответ.
Проверка возвращаемого значения только TypeInfo(T)
не поможет вам в этой ситуации, так как вам придется проверять определенные типы. Вместо этого вы можете использовать GetTypeKind(T) (XE7+), проверяя типы tkInteger
, tkInterface
и tkClass
и соответствующим образом обрабатывать их данные.
Например:
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;
Однако, как вы можете видеть выше, с TListHelper.Deserialize()
есть проблема. Сначала вам необходимо создать экземпляр объекта, чтобы вызвать на нем (T)ILObject.Deserialize()
, поэтому вам нужно знать, какой тип класса создавать. Но вы не можете вызвать T.Create
, поскольку T
не имеет ограничения class
. Таким образом, вам придется записать имя класса в свой Buffer
, прочитать его обратно и использовать отдельную фабрику для создания и возврата нового экземпляра (T)ILObject
для указанного имени класса, например:
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;
Вам придется реализовать CreateILObject()
самостоятельно, например:
function CreateILObject(AClassName: string): TILObject;
begin
if AClassName = 'TChild' then
Result := TChild.Create
...
else
Result := nil; // or raise
end;
Есть способы обобщить такую фабрику, но это выходит за рамки данного вопроса. На StackOverflow (и других форумах) есть много других вопросов, связанных с (де)сериализацией объектов.
Вы уже знаете, как создать
TValue
из элемента списка.TValue
имеет методыIsObject
,IsInstanceOf
иAsObject
. Таким образом, вы можете получить доступ к элементу списка как к объекту, при необходимости привести объект кILObject/TILObject
, а затем вызвать его методыSerialize()
/Deserialize()
.