Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assigning to fields in array property in delphi

Tags:

delphi

I have a class which stores generic items in an array. These items are accessed by a default array property:

TMyList<TData> = class
private
  items: array of TItem<TData>;
public
  function get(position: integer): TData;
  procedure edit(position: integer; data: TData);
  property Values[position: integer]: TData read get write edit; default;
end;

implementation
function TMyList<TData>.get(position: integer): TData;
begin
result:= items[position];
end;

procedure TMyList<TData>.edit(position: integer; data: TData);
var
  item: TItem<TData>;
begin
  items[position]:= item;
end;
end;

In this case the items I am storing are all of the type TTest which also has its own properties:

TTest = record
  private
      FTest: string;
      procedure setFTest(const Value: string);
  public
    property Test: string read FTest write setFTest;
end;

implementation
procedure TColouredPoint.setFTest(const Value: String);
begin
  FTest:= Value;
end;
end;

I want to be able to change the value of FTest for an instance of TTest like this:

var
  points: TMyList<TTest>;
...
points[index].Test:= 'test';

but this doesn't do anything. There is no error message but the value of points[index].Test doesn't change.

Instead I have to do this:

var
  points: TMyList<TTest>;
  temp: TTest;
...
temp:= points[index];
temp.Test:= 'test';
points[index]:= temp;

Why does the first version not work?

like image 806
charlieb Avatar asked Jan 21 '20 15:01

charlieb


Video Answer


2 Answers

Why does the first version not work?

Consider this code

points[index].Test := 'test';

The indexed property is converted by the compiler into the a function call and so the compiler effectively compiles this:

points.get(index).Text := 'test';

Now, points.get(index) returns a copy of the TTest value. Since you don't assign that to anything, the compiler introduces a local variable to hold the return value.

So your code becomes, in effect:

var
  tmp: TTest;
...
tmp := points.get(index);
tmp.Text := 'test';

That's the last thing that is ever done with tmp, and so the modifications that you make to tmp.Text are simply discarded leaving the underlying object untouched.


This issue is pretty hard to get around when working with value types.

The generic Delphi TList<T> collection allows you direct access to the underlying array, which allows you to operate on the stored values directly.

Another approach is to use a reference rather than a value. One simple way to achieve that is to use a T that is a class rather than a record, i.e. a reference type rather than a value type.

like image 103
David Heffernan Avatar answered Oct 24 '22 14:10

David Heffernan


It is because TTest is a record. The getter of the list returns a copy of the actual record and that is what you are changing. It should work when you declare TTest as a class, but then you have to take care of creating and destroying it.

like image 38
Uwe Raabe Avatar answered Oct 24 '22 12:10

Uwe Raabe