I am trying to write a generic TList that contains entries of a certain type. Starting with David's answer to this question , I wrote this class:
Type TMERecordList<T> = Class(TList<T>) Public Type P = ^T; Private Function GetItem(Index: Integer): P; Public Procedure Assign(Source: TMERecordList<T>); Virtual; Function First: P; Inline; Function Last: P; Inline; Property Items[Index: Integer]: P Read GetItem; End; Procedure TMERecordList<T>.Assign(Source: TMERecordList<T>); Var SrcItem: T; Begin Clear; For SrcItem In Source Do Add(SrcItem); End; Function TMERecordList<T>.First: P; Begin Result := Items[0]; End; Function TMERecordList<T>.GetItem(Index: Integer): P; Begin If (Index < 0) Or (Index >= Count) Then Raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange); Result := @List[Index]; End; Function TMERecordList<T>.Last: P; Begin Result := Items[Count - 1]; End;
Having methods that return a pointer to a record works well (not perfect), because pointers to records can be used as if they were records in most cases. Using a record with properties and setters, these test cases work as expected:
TMETestRecord = Record Private FID: Word; FText: String; FValues: TIntegers; Procedure SetID(Const Value: Word); Procedure SetText(Const Value: String); Procedure SetValues(Const Value: TIntegers); Public Property ID: Word Read FID Write SetID; Property Text: String Read FText Write SetText; Property Values: TIntegers Read FValues Write SetValues; End;
Problem 1 - Modifying a Contained Record
... this test case compiles and runs, but does not work as desired:
// TestSetItemFields rl1[0].ID := 9; rl1[0].Text := 'XXXX'; rl1[0].Values := [9, 99, 999, 9999];
Modifications apply to a temporary copy of the record, and not to the one stored in the list. I know that this is a known and expected behavior, as evidenced by other issues.
But ... is there a way around him? I thought that maybe if the TMERecordList <> property. Items was a typesetter; the compiler could do what it really needs. Could you? I know that David has a solution, as the question hinted at in this ... but I can't find it on my own.
That would be nice to have, as it would allow me to use a list identical to (or almost) a list of TList objects. Having the same interface, I could easily switch from objects to records and vice versa when such a need arises.
Problem 2 - Interface Uncertainty
The presence of TList <> returns a record pointer, which creates problems with the ambiguity of the interface. Some TList <> methods accept T-parameters, and we know that these are records, they will be passed by value. So what should these methods do? Should I rethink them? I am talking specifically about these sets of methods:
- Remove and RemoveItem
- Extract and ExtractItem
- Contains IndexOf, IndexOfItem, and LastIndexOf
There is some uncertainty as to how they should test the contained elements to see if they match the value of the parameter record. A list can very well contain identical entries, and this can be a source of errors in the user code.
I tried not to get it out of TList <> so as not to use these methods, but it was a mess. I could not write a class like TList without writing my own TListHelper. Unfortunately, System.Generics.Collections has some required fields that are private, such as FCount, and cannot be used outside the device.