8

我知道你实际上不能从记录中得到任何东西,但我不知道如何用一句话来总结我的问题。如果您这样做,请编辑标题。

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

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

更改SubRecSubRec1, SubRec2... SubRecN使引用变得痛苦,但并非不可能。自从我开始寻找上述问题的替代解决方案后,我想到了类。

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

TMyClass = Class

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

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

在这一点上,我有什么选择?我应该放弃记录并在课堂上尝试吗?或者仅仅坚持记录并处理变体“限制”会不会复杂得多?我全心全意学习,如果爆炸式的课堂方法能让我更聪明,我会去做。我也刚刚研究TList过(从未使用过它),但它似乎与记录混​​合得不太好,也许可以做到,但目前这可能不在我的范围内。我愿意接受任何建议。我该怎么办?

4

3 回答 3

6

您将序列化与“通过一次BlockWrite调用将所有内容写入磁盘”混为一谈。你可以序列化任何你想要的东西,不管它是从TComponent还是TPersistent.

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

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

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

于 2012-06-28T23:13:26.553 回答
5

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

关于序列化,有几种方法可以做到。参见Delphi:以某种结构存储数据

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

另一种可能性是在其定义的开头存储该类型的记录,例如,使用 aRecType: 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,但它支持继承 - 请参阅这篇文章。它是 Turbo Pascal 5.5 天中的旧式 OOP 编程,已弃用,但仍然可用。请注意,我在较新版本的 Delphi 上发现了一个奇怪的编译问题:有时,object堆栈上的分配并不总是被初始化。

Take a look at our TDynArray wrapper and its associated functions, who is able to serialize any record content, into binary or JSON. See Delphi (win32) serialization libraries question. It will work with variant records, even if they include a string in their unvariant part, whereas a plain "Write/BlockWrite" won't work with reference counted fields.

于 2012-06-29T07:44:34.787 回答
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;
于 2012-06-28T23:10:27.180 回答