我正在尝试编写包含特定类型记录的通用TList。从大卫对这个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;
使用返回指向记录的指针的方法效果很好(不完美),因为指针可以像大多数用例中的记录一样使用。使用包含属性和setter的记录,这些测试用例按预期工作:
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;
...此测试用例编译并运行但不能按预期工作:
// TestSetItemFields
rl1[0].ID := 9;
rl1[0].Text := 'XXXX';
rl1[0].Values := [9, 99, 999, 9999];
修改应用于记录的临时副本,而不是应用于列表中存储的副本。我知道这是一种已知的和预期的行为,如其他问题所述。
但是......有办法解决吗?我在想,如果TMERecordList&lt;&gt; .Items属性有一个setter,编译器可能会执行实际需要的操作。可以吗?我知道大卫有一个解决方案,正如question所暗示的那样......但我似乎无法自己找到它。
这真的很棒,因为它可以让我有一种方法使用与TList对象相同(或几乎)的列表。拥有相同的界面意味着我可以在需要时轻松地从对象更改为记录,反之亦然。
拥有TList&lt;&gt;返回一个记录指针确实会造成一些界面模糊问题。一些TList&lt;&gt;方法接受T参数,我们知道作为记录,这些将通过值传递。那么这些方法应该做什么呢?我应该重新考虑一下吗?我正在具体谈论这些方法:
对于如何测试包含的项目以查看它们是否与参数记录值匹配,存在一些模糊性。该列表很可能包含相同的记录,这可能成为用户代码中的错误来源。
我试图不从TList&lt;&gt;中获取它,以便不使用这些方法,但它是一团糟。我不能写一个类似于TList的类而不写自己的TListHelper。不幸的是,System.Generics.Collections的一个必需的字段是私有的,如FCount,并且不能在单元外使用。
答案 0 :(得分:3)
问题1
您的Items属性未标记为默认值。因此,您的错误代码正在获取基类默认属性。将default
关键字添加到您的Items属性:
property Items[Index: Integer]: P read GetItem; default;
问题2
这实际上是从TList<T>
派生的结果。我不建议这样做。封装TList<T>
的实例,因此显式定义接口而不是继承它。或者直接在代码中实现列表功能。毕竟,它只不过是一个动态数组的包装器。
对于我的课程的价值而言,根本不使用TList<T>
这是我在Emba在最近的一个版本中打破课程时非常满意的决定。
答案 1 :(得分:0)
在class A
def main_method
method1
end
protected
def method1
puts "hello from #{self.class}"
end
end
class B < A
def main_method
method1
end
end
class C < A
def main_method
self.method1
end
end
中的最新版本TList<T>
中包含System.Generics.Collections
属性,可让您直接访问列表的后备数组。您可以使用它来操作列表中的记录。