我知道你实际上不能从记录中删除任何内容,但我不确定如何用一句话来概括我的问题。如果你这样做,请编辑标题。
我想要做的是创建一个通用类型的数组,它可以是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
(从未使用它),但似乎它与记录混合得不是很好,也许它可以完成,但这可能不属于我的联盟时刻。我愿意接受任何建议。我该怎么办?
答案 0 :(得分:6)
您将序列化与“通过一次BlockWrite
调用将所有内容写入磁盘混为一谈”。您可以序列化您想要的任何内容,无论它是来自TComponent
还是TPersistent
。
虽然用一个BlockWrite
调用编写所有内容一开始看起来很方便,但是如果你想要的记录类型要存储任何特别有趣的内容(比如字符串,动态数组,那么你很快就会发现它并不是你想要的)。接口,对象或其他基于引用或指针的类型。)
您可能还会发现变体记录不满意,因为您将编码到最低的公分母。如果不检查实际包含的类型,您将无法访问记录中的任何内容,即使最小数据量的大小也将占用与最大数据类型相同的空间量。
这个问题似乎描述了多态,所以你也可以接受语言已经提供的内容。使用对象的数组(或列表或任何其他容器)。然后,您可以使用虚拟方法统一处理它们。如果需要,您可以为记录实现动态调度(例如,为每个记录提供一个函数指针,该函数指针指向一个知道如何处理该记录的包含数据类型的函数),但最后您可能会发现自己重新发明类
答案 1 :(得分:5)
处理此类数据的“自然”方式是使用class
,而不是record
。在定义时和处理实现时,它将更容易使用:特别是,virtual
方法非常强大,可以为特定类的类定制进程。然后在较新版本的Delphi中使用TList/TObjectList
或TCollection
或基于泛型的数组来存储列表。
关于序列化,有几种方法可以做到。见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;