频道分类

Delphi 10.4 自定义托管记录

作者:admin 来源: 日期:2020/5/28 14:51:34 人气: 标签:

 

Delphi中的记录可以具有任何数据类型的字段。当一条记录具有普通(非托管)字段(如数字或其他枚举值)时,对于编译器没有太多工作要做。创建和处理记录包括分配内存或摆脱内存位置。

如果记录具有由编译器管理的类型的字段(例如字符串或接口),则编译器需要注入额外的代码来管理初始化或终结。例如,对字符串进行引用计数,因此当记录超出范围时,记录内的字符串需要减少其引用计数,这可能导致为该字符串取消分配内存。因此,当您在代码的一部分中使用这种托管记录时,编译器会自动在该代码周围添加一个try-finally块,并确保即使出现异常也清除了数据。长期以来就是这种情况。换句话说,托管记录已成为Delphi语言的一部分。

带有初始化和终结运算符的记录
除了编译器对托管记录所做的默认操作之外,Delphi记录类型还支持自定义的初始化和完成。您可以使用自定义的初始化和完成代码声明记录,而不管其字段的数据类型如何,并且可以编写此类自定义的初始化和完成代码。这可以通过在记录类型中添加特定的新运算符来实现(如果需要,可以有一个运算符而没有另一个)。以下是一个简单的代码段:

type
  TMyRecord = record
    Value: Integer;
    class operator Initialize (out Dest: TMyRecord);
    class operator Finalize (var Dest: TMyRecord);
  end;
请记住,您需要为两个类方法都编写代码。例如,当记录它们的执行或初始化记录值时,我们还将记录对内存位置的引用,以查看哪个记录正在执行每个单独的操作:

class operator TMyRecord.Initialize (out Dest: TMyRecord);
begin
  Dest.Value := 10;
  Log('created' + IntToHex (Integer(Pointer(@Dest))));
end;

class operator TMyRecord.Finalize (var Dest: TMyRecord);
begin
  Log('destroyed' + IntToHex (Integer(Pointer(@Dest))));
end;
这种构造与以前可用于记录的构造之间的巨大差异是自动​​调用。如果您编写类似下面的代码的代码,则可以调用Initializer和Finalizer,最后得到由编译器为您的托管记录实例生成的try-finally块。

procedure LocalVarTest;
var
  my1: TMyRecord;
begin
  Log (my1.Value.ToString);
end;
使用此代码,您将获得如下日志:

created 0019F2A8

10

destroyed 0019F2A8

另一种情况是使用内联变量,例如:

begin
  var t: TMyRecord;
  Log(t.Value.ToString);
这会使您在日志中获得相同的顺序。

赋值运算符
:=分配复制记录字段的所有数据。尽管这是一个合理的默认值,但是当您具有自定义数据字段和自定义初始化时,您可能想要更改此行为。因此,对于“自定义托管记录”,您还可以定义一个赋值运算符。使用:=语法调用new运算符,但将其定义为Assign:

type
  TMyRecord = record
    Value: Integer;
    class operator Assign (var Dest: TMyRecord; 
 const [ref] Src: TMyRecord);
运算符定义必须遵循非常精确的规则,包括将第一个参数作为参考参数,将第二个参数作为通过引用传递的const。如果不这样做,则编译器将发出如下错误消息:

[dcc32 Error] E2617 First parameter of Assign operator must be a var parameter of the container type

[dcc32 Hint] H2618 Second parameter of Assign operator must be a const[Ref] or var parameter of the container type

有一个示例案例,调用了Assign运算符:

var
  my1, my2: TMyRecord;
begin
  my1.Value := 22;
  my2 := my1;
生成此日志(记录中包含序列号):

created 5 0019F2A0

created 6 0019F298

5 copied to 6

destroyed 6 0019F298

destroyed 5 0019F2A0

注意,破坏顺序与构造顺序相反。

像上面的示例一样,如果将赋值运算符与赋值操作结合使用,也可以将赋值运算符与初始化内联变量一起使用。这里有两种不同的情况:

var
  my1: TMyRecord;
begin
  var t := my1;
  Log(t.Value.ToString);

  var s: TMyRecord;
  Log(s.Value.ToString);
该日志:

created 6 0019F2A8

created 7 0019F2A0

6 copied to 7

10

created 8 0019F298

10

destroyed 8 0019F298

destroyed 7 0019F2A0

destroyed 6 0019F2A8

在第一种情况下,创建和分配就像在具有非局部变量的常规方案中一样发生。在第二种情况下,只有定期初始化。

将托管记录作为参数传递
当作为参数传递或由函数返回时,托管记录也可以不同于常规记录。以下是显示各种情况的例程:

procedure ParByValue (rec: TMyRecord);
procedure ParByConstValue (const rec: TMyRecord);
procedure ParByRef (var rec: TMyRecord);
procedure ParByConstRef (const [ref] rec: TMyRecord);
function ParReturned: TMyRecord;
每个日志执行以下操作:

ParByValue创建一个新记录,并调用赋值运算符(如果有)来复制数据,并在退出过程时销毁临时副本。
ParByConstValue不进行复制,也不进行任何调用。
ParByRef不进行复制,不进行调用。
ParByConstRef不进行复制,不进行调用。
ParReturned创建一个新记录(通过Initialize),并在返回时调用Assign运算符(如果调用如下),并删除临时记录:
my1 := ParReturned;
例外和托管记录
当引发异常时,与对象不同,即使没有显式的try,finally块存在,通常也会清除记录。这是根本的区别,也是托管记录真正有用的关键。

procedure ExceptionTest;
begin
  var a: TMRE;
  var b: TMRE;
  raise Exception.Create('Error Message');
end;
在此过程中,有两个构造函数调用和两个析构函数调用。同样,这是托管记录的根本区别和关键特征。有关基于托管记录的简单智能指针,请参见下一部分。

另一方面,如果在托管记录的初始化程序中引发了异常,则与常规对象不同,不会调用匹配的析构函数。

托管记录数组
如果定义托管记录的静态数组,则会在点声明处进行初始化,以调用Initialize运算符:

var
  a1: array [1..5] of TMyRecord; // call here
begin
  Log ('ArrOfRec');
他们超出范围时都被摧毁。如果定义托管记录的动态数组,则将使用数组大小​​(带有SetLength)的数组调用初始化代码:

var
  a2: array of TMyRecord;
begin
  Log ('ArrOfDyn');  
  SetLength(a2, 5); // call here

http://www.delphifmx.com/node/74

上一篇:Delphi 历史版本下载合集下一篇:没有资料