5
votes

I want to use a generic TList of records with a sub list in Delphi XE5:

type
  TMyRecord=record
    Value1: Real;
    SubList: TList<Integer>;
  end;

  TMyListOfRecords=TList<TMyRecord>;

var
  MyListOfRecords: TMyListOfRecords;

Assignments to the field of the records are not possible:

MyListOfRecords[0].Value1:=2.24; 

or

MyListOfRecords[0].SubList:=TList<Integer>.Create;

will result in "left side cannot be assigned to" error by the compiler.

See also: How to modify TList<record> value?

The following workaround works:

AMyRecord:=MyListOfRecords[0];
AMyRecord.Value1:=2.24;
AMyRecord.SubList:=TList<Integer>.Create;
AMyRecord.SubList.Add(33);
MyListOfRecords[0]:=AMyRecord;

Because of performance issues I would like to avoid to copy the data to the temporary AMyrecord. I would rather like to access the record fields and the sub list directly.

What is the best way to handle this?

1

1 Answers

6
votes

The list exposes its internal storage, which is a dynamic array, through the List property. So you can write:

MyListOfRecords.List[0].Value1 := 2.24; 

Whether this makes any measurable difference in performance in comparison to the alternative with value copies, I cannot tell. It would be worthwhile checking that.

As @LURD correctly says, List returns the internal storage. And this may have more than Count elements. Specifically it has Capacity elements. So, if you use it, you must access the elements using array indexing, over elements 0 to Count-1. Remember also that modifications to the size of the list may involve a re-allocation and so the internal storage may move. Any reference you take to List is only valid until the next re-allocation.

These warnings should suggest to you that you only consider using List if performance constraints demand so. And even then, use it sparingly.

In my codebase, I have an alternative to TList<T> whose Items[] property returns a pointer to the element. The container still stores as a dynamic array for efficient memory layout. I preferred this option to the List property because I felt it led to cleaner code.


OK, you asked to take a look at my list class that returns pointers to the elements. Here it is:

type
  TReferenceList<T> = class(TBaseValueList<T>)
  type
    P = ^T;
  private
    function GetItem(Index: Integer): P;
  public
    property Items[Index: Integer]: P read GetItem; default;
  public
    // .... helper types for enumerators excised
  public
    function GetEnumerator: TEnumerator;
    function Enumerator(Forwards: Boolean): TEnumeratorFactory;
    function ReverseEnumerator: TEnumeratorFactory;
    function IndexedEnumerator: TIndexedEnumeratorFactory;
  end;

Now, some explanation needed. The base class, TBaseValueList<T> is my alternative to TList<T>. You could substitute TList<T> if you wish. I don't because my base class does not have an Items property. That's because I want the specialized classes to introduce it. My other specialization is:

type
  TValueList<T> = class(TBaseValueList<T>)
  private
    function GetItem(Index: Integer): T;
    procedure SetItem(Index: Integer; const Value: T);
  public
    property Items[Index: Integer]: T read GetItem write SetItem; default;
  end;

The implementation of my TBaseValueList<T> is pretty obvious. It's very similar to TList<T>. I don't think you really need to see any of the implementation. It's all very obvious.

As a simple way to get a reference to an element, you could wrap List up like this:

type
  TMyList<T> = class(TList<T>)
  public
    type
      P = ^T;
  private
    function GetRef(Index: Integer): P;
  public
    property Ref[Index: Integer]: P read GetRef;
  end;

function TMyList<T>.GetRef(Index: Integer): P;
begin
  Result := @List[Index];
end;

If you want a richer set of containers than is provided by Delphi, you might care to look at Spring4D. Although I'm not sure if they have anything like my container that returns references.