Reputation: 3921
I encountered a problem some days ago while working with Generic TList in the middle of a project. I tested it in a simple test project and got the same problem. Here is the exact code:
type
TMyPoint = record
x: Integer;
y: Integer;
end;
TShape = record
Corners: TList<TMyPoint>;
end;
TForm1 = class(TForm)
Button1: TButton;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
Shape_List: TList<TShape>;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
Shape: TShape;
FirstPoint: TMyPoint;
SecondPoint: TMyPoint;
Temp: TMyPoint;
begin
// Create the corners list
Shape.Corners := TList<TMyPoint>.Create;
// Add the first point to corners
FirstPoint.x := 10;
FirstPoint.y := 20;
Shape.Corners.Add(FirstPoint);
// Add the shape to the Shape_List
Shape_List.Add(Shape);
// Clear the shape corners
Shape.Corners.Clear;
// Add another point to corners
SecondPoint.x := 100;
SecondPoint.y := 200;
Shape.Corners.Add(SecondPoint);
// Show the x of the first point of the first shape
Label1.Caption := IntToStr(Shape_List[0].Corners[0].x);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Shape_List := TList<TShape>.Create;
end;
The Label1.Caption will be 100 not 10 ! Why is it like this? I thought TList.Add(const value)
is pass-by-value not pass-by-reference!
Upvotes: 2
Views: 1086
Reputation: 25678
Added some comments to your procedure to point out exactly where the "bug" is.
procedure TForm1.Button1Click(Sender: TObject);
var
Shape: TShape;
FirstPoint: TMyPoint;
SecondPoint: TMyPoint;
Temp: TMyPoint;
begin
// Create the corners list
Shape.Corners := TList<TMyPoint>.Create; // We create a new TList<TMyPOint> OBJECT. Shape.Corners is a reference
// Add the first point to corners
FirstPoint.x := 10;
FirstPoint.y := 20;
Shape.Corners.Add(FirstPoint); // Add FirstPoint to the list we created at step 1.
// Add the shape to the Shape_List
Shape_List.Add(Shape); // Add a copy of the Shape record to the Shape_List
// Clear the shape corners
Shape.Corners.Clear; // Clear the Shape.Corners reference. This effectively clears the list of corners
// in the Shape you just added to Shape_List because it contains the same
// reference.
// Add another point to corners
SecondPoint.x := 100;
SecondPoint.y := 200;
Shape.Corners.Add(SecondPoint); // Add a new point to the Corners list. Remamber, Corners is actually
// a reference. The first Shape you added to Shape_List contains a copy
// of this exact same reference, so you're effectively adding a first point
// to both Shape.Corners and Shape_List[0].Corners.
// Show the x of the first point of the first shape
Label1.Caption := IntToStr(Shape_List[0].Corners[0].x); // Yup, you just added that point, so you get 100
end;
Upvotes: 4
Reputation: 37221
After adding the FirstPoint
, you Clear
the Corners
(after that, the list is empty). Then you add SecondPoint
which becomes the first element (index 0).
Edit: To illustrate:
var
Shape1, Shape2: TShape;
begin
Shape1.Corners := TList<TMyPoint>.Create;
Shape2 := Shape1;
OutputDebugString(PChar(Format('Shape1.Corners: $%.8x', [Integer(Shape1.Corners)])));
OutputDebugString(PChar(Format('Shape2.Corners: $%.8x', [Integer(Shape2.Corners)])));
end;
Shape1.Corners
and Shape2.Corners
point to the same list.
Upvotes: 4
Reputation: 2757
No, it is passed by reference since it is passed as a constant parameter (see: http://docwiki.embarcadero.com/VCL/en/Generics.Collections.TList.Add).
Upvotes: 1