Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi : Why TStringlist can't assign in procedure?

The first procedure :

procedure TestOne(List : TStringList);
var
  TempList : TStringList;
begin
  TempList := TStringList.Create;
  TempList.Add('Test');
  List := TempList;
  TempList.Free;
end;

procedure TForm1.Button1Click(Sender : TObject);
var
  aList : TStringList;
begin
  aList := TStringList.Create;
  TestOne(aList);
  Memo1.Lines := aList;
end;

When I click button the memo doesn't show any thing , and the breakpoint show the procedure doesn't execute this line:

  List := TempList;

this line

and I modify the procedure :

procedure TestTwo(List : TStringList);
var
  TempList : TStringList;
begin
  TempList := TStringList.Create;
  TempList.Add('Test');
  List.Text := TempList.Text;
  //or
  List.Assign(TempList);
  //List := TempList;
  TempList.Free;
end;

this time it works.

so why it can't use List := TempList; ?

like image 985
Hanlin Avatar asked Jan 23 '13 03:01

Hanlin


1 Answers

When you pass a variable by value, Delphi makes a copy of the parameter value in the Stack and all the changes made to that parameter inside the method are done over that copy.

That's the way pascal worked since Turbo Pascal days and maybe since the beginning. Consider this:

procedure TestInt(Int: Integer);
begin
  Int := 10;
  Writeln(Int); //writes 10
end;

var
  I: Integer;
begin
  I := 5;
  Wirteln(I); //writes 5
  TestInt(I);
  Writeln(I); //also writes 5
end.

Now, when you pass a object as a parameter, you have to remember the object variable is a reference to the object (a pointer to the address where the object is really stored in the heap). But, if you pass the parameter by reference, the above rule still applies: A copy of the reference is made in the stack. Any change you make to that reference inside your method/procedure is done over that copy.

The line List := TempList; merely changes the reference making it point to a different object in a different memory location. That value is lost when the procedure returns, in the same way the integer value is lost when the TestInt procedure returns.

The fact that the line is never executed is the optimizer in action. Since the new value is never used, the optimizer eliminates the assignment from the final exe and the line is really never executed.

You can change the parameter declaration to pass it by reference (a var parameter), but that's not a good Idea when working with objects, since you have to take into account who's responsible to free the object's memory to avoid memory leaks.

You don't tell what you're trying to accomplish, but it looks like Assign is the way to go, since the assignment copies the strings from one list to the other. You have to consider working directly over the List and not use a TempList, like this:

procedure TestOne(List : TStringList);
begin
  List.Clear;
  List.Add('Test');
end;

procedure TForm1.Button1Click(Sender : TObject);
var
  aList : TStringList;
begin
  aList := TStringList.Create;
  TestOne(aList);
  Memo1.Lines := aList;
end;

The result, as you can see, is the same.

Edit

Rob pointed something important in the comments, so I will address why the last line of the Button1Click method works: Memo1.Lines := aList;

This looks like a direct assignment, but you must know in that line you're dealing with a Delphi property.

A property, like a field, defines an attribute of an object. But while a field is merely a storage location whose contents can be examined and changed, a property associates specific actions with reading or modifying its data. Properties provide control over access to an object's attributes, and they allow attributes to be computed.

The declaration of a property specifies a name and a type, and includes at least one access specifier.

Looking at how the Lines property of a TCustomMemo is declared:

  TCustomMemo = class(TCustomEdit)
  ..
  ..
    property Lines: TStrings read FLines write SetLines;

This means that, when you assign a value to the property, you're really calling the SetLines method, passing the Value as a parameter, like this:

  Memo1.SetLines(AList);

The SetLines implementation looks like this:

procedure TCustomMemo.SetLines(Value: TStrings);
begin
  FLines.Assign(Value);
end;

So, you're finally issuing the same TStrings.Assign call which copies all the strings from the source list to the destination list.

This is the Delphi way to deal with object properties and maintain a clear ownership over the objects. Each component creates and owns it's own sub-objects, and you create and own your objects.

The assignment operator ( := ) over a property is sintactic sugar to allow the component writer to introduce side effects when reading or writing the values, and to allow you, the programmer to think as if the property was a standard field and enjoy the comfort of that side effects.

like image 199
jachguate Avatar answered Nov 01 '22 13:11

jachguate