I have a generic list of records. these records contains a dynamic array like following
Type
TMyRec=record
MyArr:Array of Integer;
Name: string;
Completed: Boolean;
end;
var
MyList:TList<TMyRec>;
MyRec:TMyRec;
then I create the list and set the array length like followings
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);
then i change the data in MyArr
and i also change MyRec.Name
and add another item to the list
MyRec.MyArr[0]:=5; // just for demonstration
MyRec.Name:='Record 2';
MyRec.Completed:=false;
MyList.Add(MyRec);
when MyRec.MyArr
changes after adding the first item to the list, MyArr
which is stored to the list also changes. however the other record fields does not.
My question is how to prevent the changes in MyRec.MyArr
to be reflected on the array which is already stored in the list item.
do i need to declare multiple records.
This example can be simplified like so, removing all reference to generics:
{$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.
The output is:
42 666
The reason for this is that a dynamic array is a reference type. When you assign to a variable of dynamic array type, you are taking another reference and not making a copy.
You can resolve this by forcing a reference to be unique (that is have just a simple reference). There are a number of ways to achieve this. For instance, you can call SetLength
on the array that you want to be unique.
{$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.
Output:
42 42
So, in your code you can write it like this:
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);
You can use a variety of other ways to enforce uniqueness, including Finalize
, assigning nil
, Copy
, etc.
This issue is covered in some detail in the documentation. Here are the pertinent excerpts:
If X and Y are variables of the same dynamic-array type, X := Y points X to the same array as Y. (There is no need to allocate memory for X before performing this operation.) Unlike strings and static arrays, copy-on-write is not employed for dynamic arrays, so they are not automatically copied before they are written to. For example, after this code executes:
var A, B: array of Integer; begin SetLength(A, 1); A[0] := 1; B := A; B[0] := 2; end;
the value of A[0] is 2. (If A and B were static arrays, A[0] would still be 1.) Assigning to a dynamic-array index (for example, MyFlexibleArray[2] := 7) does not reallocate the array. Out-of-range indexes are not reported at compile time. In contrast, to make an independent copy of a dynamic array, you must use the global Copy function:
var A, B: array of Integer; begin SetLength(A, 1); A[0] := 1; B := Copy(A); B[0] := 2; { B[0] <> A[0] } end;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With