Delphi, как сериализовать и десериализовать TObject

У меня есть следующий код, который занимается 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()?

Вы уже знаете, как создать TValue из элемента списка. TValue имеет методы IsObject, IsInstanceOf и AsObject. Таким образом, вы можете получить доступ к элементу списка как к объекту, при необходимости привести объект к ILObject/TILObject, а затем вызвать его методы Serialize()/Deserialize().

Remy Lebeau 26.06.2024 19:38

@RemyLebeau, спасибо: if TypeInfo(T) = TypeInfo(Integer) как использовать то же самое с ILObject? Это правильно if TypeInfo(T) = TypeInfo(TILObject) ?

Sdean 26.06.2024 19:56

Не используйте TypeInfo(T) = TypeInfo(Integer), вместо этого используйте GetTypeKind(T) = tkInteger. Затем вы можете проверить типы tkInterface и tkClass. Во-вторых, вы также можете создать TValue безоговорочно, а затем использовать его свойство Kind.

Remy Lebeau 26.06.2024 20:05

Однако у вас могут возникнуть проблемы с вашим TListHelper.Deserialize(). Вам нужно создать экземпляр объекта для вызова (T)ILObject.Deserialize(), поэтому вам нужно знать, какой тип класса создавать. Вы не можете вызвать T.Create, поскольку T не имеет ограничения class. Таким образом, вам придется записать имя класса в свой Buffer (просто), а затем прочитать его обратно (просто), а затем использовать отдельную фабрику для создания и возврата экземпляра ILObject/TILObject из указанного имени класса (большая работа).

Remy Lebeau 26.06.2024 20:28

@RemyLebeau большое спасибо; более подробную информацию (ЕСЛИ ВОЗМОЖНО, ПОЖАЛУЙСТА) на: «o вам придется записать имя класса в ваш буфер (легко), а затем прочитать его обратно (легко), а затем использовать отдельную фабрику для создания и возврата экземпляра ILObject/TILObject из указанное имя класса (много работы)"

Sdean 26.06.2024 20:48

Я опубликовал ответ.

Remy Lebeau 26.06.2024 21:03
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
298
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проверка возвращаемого значения только 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 (и других форумах) есть много других вопросов, связанных с (де)сериализацией объектов.

Другие вопросы по теме