德尔福的“降序”记录?

时间:2012-06-28 21:31:35

标签: delphi records

我知道你实际上不能从记录中删除任何内容,但我不确定如何用一句话来概括我的问题。如果你这样做,请编辑标题。

我想要做的是创建一个通用类型的数组,它可以是X个类型中的一个,数组将填充那些自定义类型(它们具有不同的字段,这是重要的)。简单的方法是只创建一个变量记录数组,每个变体都有自己的类型,但显然不能像这样重新声明标识符:

GenericRec = Record
  case SubTypeName: TSubTypeName of
    type1name: (SubRec: Type1);
    type2name: (SubRec: Type2);
    ...
    typeNname: (SubRec: TypeN);
  end;

SubRec更改为SubRec1, SubRec2... SubRecN会使引用变得痛苦,但并非不可能。 因为我开始寻找上述问题的替代解决方案,所以我想起了课程。

显示我想要实现的内容的明显示例是TObject,其中的数组可以分配给许多不同的东西。这就是我想要的,但有记录(这是不可能做到的),因为我希望能够将记录保存到文件中并将其读回(也因为它是我已经熟悉的东西)。制作我自己的简单类并不是一个问题,从中创建一个后代类来表示我的子类型 - 我可以这样做。但是如何写入文件并将其读回来呢?这归结为序列化,我不知道该怎么做。从我收集的内容来看,它并不容易,而且课程必须来自TComponent

TMyClass = Class

如果我像上面那样上课,会有什么不同吗?它没什么特别的,最多有10个字段,包括一些自定义类型。

将序列化放在一边(仅仅因为我有很多关于该主题的阅读),在这里使用类也可能是不可能的。

此时,我的选择是什么?我应该放弃记录并在课堂上尝试这个吗?或者只是坚持记录并处理变体“限制”会不那么复杂?我只是在学习,如果课程方法的爆炸可能让我更聪明,我会做的。我也只是调查了TList(从未使用它),但似乎它与记录混合得不是很好,也许它可以完成,但这可能不属于我的联盟时刻。我愿意接受任何建议。我该怎么办?

3 个答案:

答案 0 :(得分:6)

您将序列化与“通过一次BlockWrite调用将所有内容写入磁盘混为一谈”。您可以序列化您想要的任何内容,无论它是来自TComponent还是TPersistent

虽然用一个BlockWrite调用编写所有内容一开始看起来很方便,但是如果你想要的记录类型要存储任何特别有趣的内容(比如字符串,动态数组,那么你很快就会发现它并不是你想要的)。接口,对象或其他基于引用或指针的类型。)

您可能还会发现变体记录不满意,因为您将编码到最低的公分母。如果不检查实际包含的类型,您将无法访问记录中的任何内容,即使最小数据量的大小也将占用与最大数据类型相同的空间量。

这个问题似乎描述了多态,所以你也可以接受语言已经提供的内容。使用对象的数组(或列表或任何其他容器)。然后,您可以使用虚拟方法统一处理它们。如果需要,您可以为记录实现动态调度(例如,为每个记录提供一个函数指针,该函数指针指向一个知道如何处理该记录的包含数据类型的函数),但最后您可能会发现自己重新发明类

答案 1 :(得分:5)

处理此类数据的“自然”方式是使用class,而不是record。在定义时和处理实现时,它将更容易使用:特别是,virtual方法非常强大,可以为特定类的类定制进程。然后在较新版本的Delphi中使用TList/TObjectListTCollection或基于泛型的数组来存储列表。

关于序列化,有几种方法可以做到。见Delphi: Store data in somekind of structure

在您的特定情况下,困难来自您正在使用的“变体”类型的记录。恕我直言,主要缺点是编译器将拒绝在“变体”部分中设置任何引用计数类型的变量(例如string)。因此,您只能在此“变体”部分中编写“普通”变量(如integer)。恕我直言,这是一个很大的限制,它降低了对这种解决方案的兴趣。

另一种可能性是在其定义的开头存储记录类型,例如:使用RecType: integer或更好的RecType: TEnumerationType,这将比数字更明确。但是你必须手工编写很多代码,并使用指针,如果你的指针编码不是很流畅的话,这有点容易出错。

因此,您还可以存储记录的类型信息,可通过TypeInfo(aRecordVariable)访问。然后你可以使用FillChar将记录内容初始化为零,在分配之后,然后使用以下函数来完成记录内容,就在重新分配之后(这是Dispose()内部所做的事情,你应该叫它,否则你会泄漏内存):

procedure RecordClear(var Dest; TypeInfo: pointer);
asm
  jmp System.@FinalizeRecord
end;

但是这样的实施模式只会重新发明轮子!事实上class是如何实现的:任何TObject实例的第一个元素是指向其ClassType的指针:

function TObject.ClassType: TClass;
begin
  Pointer(Result) := PPointer(Self)^;
end;

Delphi中还有另一种结构,称为object。它是某种record,但它支持继承 - 请参阅this article。这是Turbo Pascal 5.5天的旧式OOP编程,已被弃用,但仍然可用。请注意,我发现了weird compilation issue on newer versions of Delphi:有时,堆栈上分配的object并不总是初始化。

查看我们的TDynArray包装器及其相关函数,它们能够将任何record内容序列化为二进制或JSON。请参阅Delphi (win32) serialization libraries问题。它将与变体记录一起使用,即使它们在其不变部分中包含string,而普通的“Write / BlockWrite”也不适用于引用计数字段。

答案 2 :(得分:2)

要使用记录执行此操作,您将创建在前面具有公共字段的不同记录类型,然后将这些相同的字段放在通用记录中。然后,您可以在需要时简单地将指向通用记录的指针转换为指向特定记录的指针。例如:

type
  PGenericRec = ^GenericRec;
  GenericRec = Record 
    RecType: Integer;
  end;

  PType1Rec = ^Type1Rec; 
  Type1Rec = Record 
    RecType: Integer;
    // Type1Rec specific fields...
  end;

  PType2Rec = ^Type2Rec; 
  Type2Rec = Record 
    RecType: Integer;
    // Type2Rec specific fields...
  end;

  PTypeNRec = ^TypeNRec;
  TypeNRec = Record
    RecType: Integer;
    // TypeNRec specific fields...
  end; 

var
  Recs: array of PGenericRec;
  Rec1: PType1Rec; 
  Rec2: PType2Rec; 
  RecN: PTypeNRec;
  I: Integer;
begin
  SetLength(Recs, 3);

  New(Rec1);
  Rec1^.RecType := RecTypeForType1Rec;
  // fill Rec1 fields ...
  Recs[0] := PGenericRec(Rec1);

  New(Rec2);
  Rec2^.RecType := RecTypeForType2Rec;
  // fill Rec2 fields ...
  Recs[1] := PGenericRec(Rec2);

  New(RecN);
  Rec3^.RecType := RecTypeForTypeNRec;
  // fill RecN fields ...
  Recs[2] := PGenericRec(RecN);

  for I := 0 to 2 do
  begin
    case Recs[I]^.RecType of
      RecTypeForType1Rec: begin
        Rec1 := PType1Rec(Recs[I]);
        // use Rec1 as needed...
      end;
      RecTypeForType1Re2: begin
        Rec2 := PType2Rec(Recs[I]);
        // use Rec2 as needed...
      end;
      RecTypeForTypeNRec: begin
        RecN := PTypeNRec(Recs[I]);
        // use RecN as needed...
      end;
    end;
  end;

  for I := 0 to 2 do
  begin
    case Recs[I]^.RecType of
      RecTypeForType1Rec: Dispose(PType1Rec(Recs[I]));
      RecTypeForType2Rec: Dispose(PType2Rec(Recs[I]));
      RecTypeForTypeNRec: Dispose(PTypeNRec(Recs[I]));
    end;
  end;
end;

至于序列化,您不需要TComponent。您可以序列化记录,只需手动执行即可。对于写入,首先写出RecType值,然后写出特定于记录的值。对于读取,首先读取RecType值,然后为该值创建适当的记录类型,然后将特定于记录的值读入其中。:

interface

type
  PGenericRec = ^GenericRec;
  GenericRec = Record 
    RecType: Integer;
  end;

  NewRecProc = procedure(var Rec: PGenericRec);
  DisposeRecProc = procedure(Rec: PGenericRec);
  ReadRecProc = procedure(Rec: PGenericRec);
  WriteRecProc = procedure(const Rec: PGenericRec);

function NewRec(ARecType: Integer): PGenericRec;
procedure DisposeRec(var Rec: PGenericRec);
procedure ReadRec(Rec: PGenericRec);
procedure WriteRec(const Rec: PGenericRec);

procedure RegisterRecType(ARecType: Integer; ANewProc: NewRecProc; ADisposeProc: DisposeRecProc; AReadproc: ReadRecFunc; AWriteProc: WriteRecProc);

implementation

type
  TRecTypeReg = record
    RecType: Integer;
    NewProc: NewRecProc;
    DisposeProc: DisposeRecProc;
    ReadProc: ReadRecProc;
    WriteProc: WriteRecProc;
  end;

var
  RecTypes: array of TRecTypeReg;

function NewRec(ARecType: Integer): PGenericRec;
var
  I: Integer;
begin
  Result := nil;
  for I = Low(RecTypes) to High(RecTypes) do
  begin
    with RecTypes[I] do
    begin
      if RecType = ARecType then
      begin
        NewProc(Result);
        Exit;
      end;
    end;
  end;
  raise Exception.Create('RecType not registered');
end;

procedure DisposeRec(var Rec: PGenericRec);
var
  I: Integer;
begin
  for I = Low(RecTypes) to High(RecTypes) do
  begin
    with RecTypes[I] do
    begin
      if RecType = Rec^.RecType then
      begin
        DisposeProc(Rec);
        Rec := nil;
        Exit;
      end;
    end;
  end;
  raise Exception.Create('RecType not registered');
end;

procedure ReadRec(var Rec: PGenericRec);
var
  LRecType: Integer;
  I: Integer;
begin
  Rec := nil;
  LRecType := ReadInteger;
  for I = Low(RecTypes) to High(RecTypes) do
  begin
    with RecTypes[I] do
    begin
      if RecType = LRecType then
      begin
        NewProc(Rec);
        try
          ReadProc(Rec);
        except
          DisposeProc(Rec);
          raise;
        end;
        Exit;
      end;
    end;
  end;
  raise Exception.Create('RecType not registered');
end;

procedure WriteRec(const Rec: PGenericRec);
var
  I: Integer;
begin
  for I = Low(RecTypes) to High(RecTypes) do
  begin
    with RecTypes[I] do
    begin
      if RecType = Rec^.RecType then
      begin
        WriteInteger(Rec^.RecType);
        WriteProc(Rec);
        Exit;
      end;
    end;
  end;
  raise Exception.Create('RecType not registered');
end;

procedure RegisterRecType(ARecType: Integer; ANewProc: NewRecProc; ADisposeProc: DisposeRecProc; AReadproc: ReadRecFunc; AWriteProc: WriteRecProc);
begin
  SetLength(RecTypes, Length(RecTypes)+1);
  with RecTypes[High(RecTypes)] do
  begin
    RecType := ARecType;
    NewProc := ANewProc;
    DisposeProc := ADisposeProc;
    ReadProc := AReadProc;
    WriteProc := AWriteProc;
  end;
end;

end.

type
  PType1Rec = ^Type1Rec; 
  Type1Rec = Record 
    RecType: Integer;
    Value: Integer;
  end;

procedure NewRec1(var Rec: PGenericRec);
var
  Rec1: PType1Rec;
begin
  New(Rec1);
  Rec1^.RecType := RecTypeForType1Rec;
  Rec := PGenericRec(Rec1);
end;

procedure DisposeRec1(Rec: PGenericRec);
begin
  Dispose(PType1Rec(Rec));
end;

procedure ReadRec1(Rec: PGenericRec);
begin
  PType1Rec(Rec)^.Value := ReadInteger;
end;

procedure WriteRec1(const Rec: PGenericRec);
begin
  WriteInteger(PType1Rec(Rec)^.Value);
end;

initialization
  RegisterRecType(RecTypeForType1Rec, @NewRec1, @DisposeRec1, @ReadRec1, @WriteRec1);

type
  PType2Rec = ^Type2Rec; 
  Type2Rec = Record 
    RecType: Integer;
    Value: Boolean;
  end;

procedure NewRec2(var Rec: PGenericRec);
var
  Rec2: PType2Rec;
begin
  New(Rec2);
  Rec2^.RecType := RecTypeForType2Rec;
  Rec := PGenericRec(Rec2);
end;

procedure DisposeRec2(Rec: PGenericRec);
begin
  Dispose(PType2Rec(Rec));
end;

procedure ReadRec2(Rec: PGenericRec);
begin
  PType2Rec(Rec)^.Value := ReadBoolean;
end;

procedure WriteRec2(const Rec: PGenericRec);
begin
  WriteBoolean(PType2Rec(Rec)^.Value);
end;

initialization
  RegisterRecType(RecTypeForType2Rec, @NewRec2, @DisposeRec2, @ReadRec2, @WriteRec2);

type
  PTypeNRec = ^Type2Rec; 
  TypeNRec = Record 
    RecType: Integer;
    Value: String;
  end;

procedure NewRecN(var Rec: PGenericRec);
var
  RecN: PTypeNRec;
begin
  New(RecN);
  RecN^.RecType := RecTypeForTypeNRec;
  Rec := PGenericRec(RecN);
end;

procedure DisposeRecN(Rec: PGenericRec);
begin
  Dispose(PTypeNRec(Rec));
end;

procedure ReadRecN(Rec: PGenericRec);
begin
  PTypeNRec(Rec)^.Value := ReadString;
end;

procedure WriteRecN(const Rec: PGenericRec);
begin
  WriteString(PTypeNRec(Rec)^.Value);
end;

initialization
  RegisterRecType(RecTypeForTypeNRec, @NewRecN, @DisposeRecN, @ReadRecN, @WriteRecN);

var
  Recs: array of PGenericRec;

procedure CreateRecs;
begin
  SetLength(Recs, 3);

  NewRec1(Recs[0]);
  PRecType1(Recs[0])^.Value : ...;

  NewRec2(Recs[1]);
  PRecType2(Recs[1])^.Value : ...;

  NewRecN(Recs[2]);
  PRecTypeN(Recs[2])^.Value : ...;
end;

procedure DisposeRecs;
begin
  for I := 0 to High(Recs) do
    DisposeRec(Recs[I]);
  SetLength(Recs, 0);
end;

procedure SaveRecs;
var
  I: Integer;
begin
  WriteInteger(Length(Recs));
  for I := 0 to High(Recs) do
    WriteRec(Recs[I]);
end;

procedure LoadRecs;
var
  I: Integer;
begin
  DisposeRecs;
  SetLength(Recs, ReadInteger);
  for I := 0 to High(Recs) do
    ReadRec(Recs[I]);
end;