我正在尝试编写一个通用的TList,其中包含特定类型的记录。从David在question上的答案开始,我编写了这个类:
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;
拥有返回指向记录的方法非常好(不是完美的),因为在大多数情况下,可以像使用记录一样使用指向记录的指针。使用具有属性和设置器的记录,这些测试用例按预期工作:
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;
// TestSetItem1
rl2[0] := rl1[0];
// TestSetItem2
r.ID := 9;
r.Text := 'XXXX';
r.Values := [9, 99, 999, 9999];
rl1[0] := r;
// TestAssignEmpty (rl0 is empty... after assign so should rl2)
rl2.Assign(rl0);
// TestAssignDeepCopies (modifications after assign should not affect both records)
rl2.Assign(rl1);
r.ID := 9;
r.Text := 'XXXX';
r.Values := [9, 99, 999, 9999];
rl1[0] := r;
问题1 - 修改包含的记录
...这个测试用例可以编译和运行,但是没有按照预期工作:
// TestSetItemFields
rl1[0].ID := 9;
rl1[0].Text := 'XXXX';
rl1[0].Values := [9, 99, 999, 9999];
修改应用于记录的临时副本,而不是存储在列表中的记录。我知道这是已知和预期的行为,如其他问题所述。
但是...有没有办法规避它?我想也许如果TMERecordList<>.Items属性有一个setter,编译器可能会做实际需要的事情。可以吗?我知道David有一个解决方案,就像在这个question中暗示的那样...但我自己似乎找不到它。
这真的很好,因为它将使我能够以与对象的TList几乎相同的方式使用列表。具有相同接口意味着我可以轻松地在对象和记录之间进行更改,当需要时。
问题2-接口歧义
TList<>返回记录指针确实会导致一些接口歧义问题。一些TList<>方法接受T参数,我们知道作为记录,这些参数将通过值传递。那么这些方法应该做什么?我该重新考虑它们吗?我特别谈论这些方法组:
- Remove和RemoveItem
- Extract和ExtractItem
- 包含IndexOf、IndexOfItem和LastIndexOf
关于这些方法如何测试包含的项是否与参数记录值匹配存在一些不确定性。列表中可能会包含相同的记录,这可能会成为用户代码中的错误源。
我尝试不从TList<>派生它,以免有这些方法,但是这很混乱。我不能编写一个类似于TList的类,而不编写自己的TListHelper。不幸的是,System.Generics.Collections的TListHelper有一些需要的私有字段,例如FCount,不能在单元外使用。
rl1.List[0]
而不是rl1[0]
。而且你没有默认属性的事实确实使得代码使用了继承的索引属性(据我所知,这实际上不会有任何影响)。 - Rudy Velthuis