Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi generics TObjectList<T> inheritance

I want to create a TObjectList<T> descendant to handle common functionality between object lists in my app. Then I want to further descend from that new class to introduce additional functionality when needed. I cannot seem to get it working using more than 1 level of inheritance. I probably need to understand generics a little bit more, but I've search high and low for the correct way to do this without success. Here is my code so far:

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

It appears that the following declaration:

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

causes the following compiler error:

[DCC Error] edGenerics.pas(106): E2010 Incompatible types: 'TCatalogueItem' and 'TIndexedObject'

However the compiler shows the error at the END of the compiled unit (line 106), not on the declaration itself, which does not make any sense to me...

Basically the idea is that I have a generic list descending from TObjectList that I can extend with new functionality on an as needs basis. Any help with this would be GREAT!!!

I should add, using Delphi 2010.

Thanks.

like image 906
Rick Wheeler Avatar asked Feb 21 '13 01:02

Rick Wheeler


1 Answers

Your error is in the type casting, and the compiler error is OK (but it fails to locate the correct file in my Delphi XE3).

Your ItemByIndex method is declared:

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

But then you have the line:

Result := TIndexedObject(nil);

This is fine for the parent class TIndexedObjectList, where the result of the function is of type TIndexedObject, but is not OK for the descendant class TCatalogueItemList, where the result of the function is of the type TCatalogueItem.

As you may know, a TCatalogueItem instance is assignment compatible with a TIndexedObject variable, but the opposite is not true. It translates to something like this:

function TCatalogueItemList.ItemByIndex(AIndex: Integer): TCatalogueItem;
begin
  Result := TIndexedObject(nil);  //did you see the problem now?

To initialize the result to a nil value, you can call the Default() pseudo-function, like this:

Result := Default(T);

In Delphi XE or greater, the solution is also generic. Rather than type-casting the result as a fixed TIndexedObjectList class, you apply a generic type casting use the T type

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

But, in this specific case, type-casting a nil constant is not needed, since nil is a special value that is assignment compatible with any reference, so you just have to replace the line with:

Result := nil;

And it will compile, and hopefully work as you expect.

like image 119
jachguate Avatar answered Oct 05 '22 23:10

jachguate