编写记录的通用TList

Fra*_*azz 6 delphi generics collections

我正在尝试编写包含特定类型记录的通用TList.从大卫对这个问题的回答开始,我写了这堂课:

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;
Run Code Online (Sandbox Code Playgroud)

使用返回指向记录的指针的方法效果很好(不完美),因为指针可以像大多数用例中的记录一样使用.使用包含属性和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;
Run Code Online (Sandbox Code Playgroud)

问题1 - 修改包含的记录

...此测试用例编译并运行但不能按预期工作:

  // TestSetItemFields
  rl1[0].ID     := 9;
  rl1[0].Text   := 'XXXX';
  rl1[0].Values := [9, 99, 999, 9999];
Run Code Online (Sandbox Code Playgroud)

修改应用于记录的临时副本,而不是应用于列表中存储的副本.我知道这是一个已知的和预期的行为,如其他问题所述.

但是......有没有办法呢?我在想,如果TMERecordList <>.Items属性有一个setter,编译器也许可以做到实际需要的东西.可以吗?我知道大卫有一个解决方案,正如在这个问题中暗示的那样......但我似乎无法自己找到它.

这真的很不错,因为它允许我有一种方法使用与TList对象相同(或几乎)的列表.拥有相同的界面意味着我可以在需要时轻松地从对象更改为记录,反之亦然.

问题2 - 界面歧义

让TList <>返回记录指针确实会引起一些界面歧义问题.一些TList <>方法接受T参数,我们知道它们是记录,它们将通过值传递.那么这些方法应该怎么做呢?我应该重新考虑一下吗?我正在特别谈论这些方法:

  • Remove和RemoveItem
  • Extract和ExtractItem
  • 包含IndexOf,IndexOfItem和LastIndexOf

关于如何测试包含的项目以查看它们是否与参数记录值匹配,存在一些模糊性.该列表很可能包含相同的记录,这可能成为用户代码中的错误来源.

我试着不是从TList <>中派生出来的,所以不要使用这些方法,但这是一团糟.我不能写一个类似于TList的类而不写自己的TListHelper.不幸的是,System.Generics.Collections的一个必需的字段是私有的,如FCount,并且不能在单元外使用.

Dav*_*nan 4

问题1

您的 Items 属性未标记为默认属性。因此,您的错误代码正在选择基类默认属性。将default关键字添加到您的 Items 属性中:

property Items[Index: Integer]: P read GetItem; default;
Run Code Online (Sandbox Code Playgroud)

问题2

这确实是源自 的结果TList<T>。我不建议这样做。封装一个实例TList<T>并因此显式定义接口而不是继承它。或者直接在代码中实现列表功能。毕竟,它只不过是动态数组的包装器。

值得一提的是,我的课程TList<T>根本不使用这个决定,当 Emba 在最近的版本中打破了该课程时,我对此感到非常满意。