问题
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:
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.
来源:https://stackoverflow.com/questions/22212783/generic-tlist-of-records-with-a-sub-list