Search code examples
delphigenericsrecordtlist

Delphi 2010: Confusing generic type TList scenario? Pass by value or reference?


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!


Solution

  • 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;