包含动态数组的通用记录列表

Kha*_*lid 5 arrays delphi generics record tlist

我有一个通用的记录列表。这些记录包含一个动态数组,如下所示

Type
  TMyRec=record
MyArr:Array of Integer;
    Name: string;
    Completed: Boolean;
  end;

var
  MyList:TList<TMyRec>;
  MyRec:TMyRec;
Run Code Online (Sandbox Code Playgroud)

然后我创建列表并设置数组长度如下

MyList:=TList<TMyRec>.Create;
SetLength(MyRec.MyArr,5);
MyRec.MyArr[0]:=8;  // just for demonstration
MyRec.Name:='Record 1';
MyRec.Completed:=true;
MyList.Add(MyRec);
Run Code Online (Sandbox Code Playgroud)

然后我更改数据MyArr,我也更改MyRec.Name并将另一个项目添加到列表中

MyRec.MyArr[0]:=5;  // just for demonstration
MyRec.Name:='Record 2';
MyRec.Completed:=false;
MyList.Add(MyRec);
Run Code Online (Sandbox Code Playgroud)

MyRec.MyArr将第一个项目添加到列表后发生变化时,MyArr存储到列表中的内容也会发生变化。但是其他记录字段没有。

我的问题是如何防止更改MyRec.MyArr反映在已存储在列表项中的数组上。

我需要声明多条记录吗?

Dav*_*nan 4

这个例子可以像这样简化,删除所有对泛型的引用:

{$APPTYPE CONSOLE}

var
  x, y: array of Integer;

begin
  SetLength(x, 1);
  x[0] := 42;
  y := x;
  Writeln(x[0]);
  y[0] := 666;
  Writeln(x[0]);
end.
Run Code Online (Sandbox Code Playgroud)

输出是:

42
第666章

原因是动态数组是引用类型。当您分配给动态数组类型的变量时,您将获取另一个引用而不是进行复制。

您可以通过强制引用唯一(即只有一个简单的引用)来解决此问题。有多种方法可以实现这一目标。例如,您可以调用SetLength您想要唯一的数组。

{$APPTYPE CONSOLE}

var
  x, y: array of Integer;

begin
  SetLength(x, 1);
  x[0] := 42;
  y := x;
  SetLength(y, Length(y));
  Writeln(x[0]);
  y[0] := 666;
  Writeln(x[0]);
end.
Run Code Online (Sandbox Code Playgroud)

输出:

42
42

所以,在你的代码中你可以这样写:

MyList:=TList<TMyRec>.Create;

SetLength(MyRec.MyArr,5);
MyRec.MyArr[0]:=8;  // just for demonstration
MyRec.Name:='Record 1';
MyRec.Completed:=true;
MyList.Add(MyRec);

SetLength(MyRec.MyArr,5); // <-- make the array unique
MyRec.MyArr[0]:=5;  // just for demonstration
MyRec.Name:='Record 2';
MyRec.Completed:=false;
MyList.Add(MyRec);
Run Code Online (Sandbox Code Playgroud)

您可以使用各种其他方法来强制唯一性,包括Finalize、 分配nilCopy等。

文档中详细介绍了这个问题。以下是相关摘录:

如果 X 和 Y 是相同动态数组类型的变量,则 X := Y 将 X 指向与 Y 相同的数组。(在执行此操作之前不需要为 X 分配内存。)与字符串和静态数组不同,复制-on-write 不适用于动态数组,因此在写入之前不会自动复制它们。例如,这段代码执行后:

 var
   A, B: array of Integer;
   begin
     SetLength(A, 1);
     A[0] := 1;
     B := A;
     B[0] := 2;
   end;
Run Code Online (Sandbox Code Playgroud)

A[0] 的值为 2。(如果 A 和 B 是静态数组,A[0] 仍为 1。)分配给动态数组索引(例如,MyFlexibleArray[2] := 7)不会重新分配数组。编译时不会报告超出范围的索引。相反,要制作动态数组的独立副本,必须使用全局 Copy 函数:

 var
   A, B: array of Integer;
 begin
   SetLength(A, 1);
   A[0] := 1;
   B := Copy(A);
   B[0] := 2; { B[0] <> A[0] }
 end;
Run Code Online (Sandbox Code Playgroud)