Delphi泛型 TObjectList<T> 继承

5

我希望创建一个TObjectList<T>的子类,以处理应用程序中对象列表的常见功能。然后,我想从这个新类进一步派生,以在需要时引入其他功能。但是,我似乎无法使用超过1级的继承使其正常工作。我可能需要更好地理解泛型,但我已经搜索了很多资料,没有找到正确的做法。以下是我的代码:

unit edGenerics;

interface

uses
  Generics.Collections;

type

  TObjectBase = class
  public
    procedure SomeBaseFunction;
  end;

  TObjectBaseList<T: TObjectBase> = class(TObjectList<T>)
  public
    procedure SomeOtherBaseFunction;
  end;

  TIndexedObject = class(TObjectBase)
  protected
    FIndex: Integer;
  public
    property Index: Integer read FIndex write FIndex;
  end;

  TIndexedObjectList<T: TIndexedObject> = class(TObjectBaseList<T>)
  private
    function GetNextAutoIndex: Integer;
  public
    function Add(AObject: T): Integer;
    function ItemByIndex(AIndex: Integer): T;
    procedure Insert(AIndex: Integer; AObject: T);
  end;

  TCatalogueItem = class(TIndexedObject)
  private
    FID: integer;
  public
    property ID: integer read FId write FId;
  end;

  TCatalogueItemList = class(TIndexedObjectList<TCatalogueItem>)
  public
    function GetRowById(AId: Integer): Integer;
  end;

implementation

uses
  Math;

{ TObjectBase }

procedure TObjectBase.SomeBaseFunction;
begin
end;

{ TObjectBaseList<T> }

procedure TObjectBaseList<T>.SomeOtherBaseFunction;
begin
end;

{ TIndexedObjectList }

function TIndexedObjectList<T>.Add(AObject: T): Integer;
begin
  AObject.Index := GetNextAutoIndex;
  Result := inherited Add(AObject);
end;

procedure TIndexedObjectList<T>.Insert(AIndex: Integer; AObject: T);
begin
  AObject.Index := GetNextAutoIndex;
  inherited Insert(AIndex, AObject);
end;

function TIndexedObjectList<T>.ItemByIndex(AIndex: Integer): T;
var
  I: Integer;
begin
  Result := Default(T);
  while (Count > 0) and (I < Count) and (Result = Default(T)) do
    if Items[I].Index = AIndex then
      Result := Items[I]
    else
      Inc(I);
end;

function TIndexedObjectList<T>.GetNextAutoIndex: Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := 0 to Count - 1 do
    Result := Max(Result, Items[I].Index);
  Inc(Result);
end;

{ TCatalogueItemList }

function TCatalogueItemList.GetRowById(AId: Integer): Integer;
var
  I: Integer;
begin
  Result := -1;
  for I := 0 to Pred(Self.Count) do
    if Self.Items[I].Id = AId then
    begin
      Result := I;
      Break;
    end;
end;

end.
/////// ERROR HAPPENS HERE ////// ???? why is beyond me

看起来以下声明:

>>> TCatalogueItemList = class(TIndexedObjectList<TCatalogueItem>) <<<<

导致以下编译器错误:
[DCC Error] edGenerics.pas(106): E2010 不兼容的类型:'TCatalogueItem' and 'TIndexedObject'
然而,编译器在编译单元结束时(第106行)显示错误,而不是在声明本身处,这对我来说没有任何意义...
基本上,我的想法是,我有一个从TObjectList继承的通用列表,我可以根据需要扩展新功能。关于此问题的任何帮助都将非常重要!
我应该补充说明,使用Delphi 2010。
谢谢。

1
如果你有代码可用的话,指出导致错误的实际代码部分会很好。这比期望我们计数或将其复制/粘贴到编辑器中要容易得多。像 //错误发生在这里//这是第106行 这样的简单注释就足够了。 - Ken White
在问题主体中添加了进一步的解释。 - Rick Wheeler
你的代码中,TIndexedObjectList<T: TIndexedObject> 不应该从 TObjectList<T> 继承,而应该从 TObjectBaseList<T: TObjectBase> 继承,这样更合适。 - ain
是的,谢谢你指出来。然而,在更正声明后,编译错误与之前完全相同。 - Rick Wheeler
@Ken:XE3 IDE 会将错误定位在文件的最后一行(final end 后面)。错误消息中不提供行号。 - jachguate
1个回答

9

你的错误在于类型转换,编译器报错是正确的(但无法在我的Delphi XE3中找到正确的文件)。

你的ItemByIndex方法被声明为:

TIndexedObjectList<T>.ItemByIndex(AIndex: Integer): T;

但是您还有这一行:

Result := TIndexedObject(nil);

这对于父类 TIndexedObjectList 很好,因为函数的结果是 TIndexedObject 类型,但是对于子类 TCatalogueItemList 不行,因为函数的结果是 TCatalogueItem 类型。可能你知道,一个 TCatalogueItem 实例可以分配给一个 TIndexedObject 变量,但反过来则不行。这就相当于:
function TCatalogueItemList.ItemByIndex(AIndex: Integer): TCatalogueItem;
begin
  Result := TIndexedObject(nil);  //did you see the problem now?

为了将结果初始化为 nil 值,您可以调用 Default() 伪函数,例如:
Result := Default(T);

在 Delphi XE 或更高版本中,解决方案也是通用的。不必将结果类型转换为固定的 TIndexedObjectList 类,而是使用 T 类进行通用类型转换。

Result := T(nil);
//or 
Result := T(SomeOtherValue);

但是,在这种特定情况下,不需要将nil常量进行类型转换,因为nil是一个特殊值,可与任何引用兼容,所以您只需要将该行替换为:

Result := nil;

它将进行编译,希望能按照您的期望工作。


非常感谢您的回答。它“几乎”可以工作,但是这一行:Result := nil会导致编译错误“不兼容的类型:'T'和'Pointer'”。这就是为什么我之前将其强制转换为TIndexedObject的原因。如何将通用类型的结果设置为nil或通用等效项? - Rick Wheeler
在XE3中它可以工作,但是如果你的编译器需要转换,请使用通用的方法:Result := T(nil);,正如答案所述。 - jachguate
谢谢您的建议,但不幸的是,在Delphi 2010中,语句Result := T(nil);会创建一个“无效类型转换”错误。因此,在Delphi 2010中似乎都没有可行的方法。有趣的是,如果我完全注释掉这一行,它就可以编译而不出错。我将进一步测试以查看当列表中不存在项目时是否返回nil。 - Rick Wheeler
2
在 Delphi 2010(或者之后的版本),为函数结果初始化泛型类型的正确方式是 Result := Default(T);。对于上述函数,这种方式似乎可以正常工作。 - Rick Wheeler
@Rick和jachguate:感谢你们提供的Default(T)。学到了新东西。 - Marjan Venema
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接