5

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.

5
  • 1
    It would be nice (since you have it available) if you would point out the actual portion of the code causing the error. It's much easier than expecting us to count or copy/paste into an editor. A simple comment like // error happens here or //this is line 106 would suffice. Commented Feb 21, 2013 at 1:16
  • added further explanation to the question body Commented Feb 21, 2013 at 1:36
  • Shouldn't the TIndexedObjectList<T: TIndexedObject> descend from TObjectBaseList<T: TObjectBase> and not from TObjectList<T> as in your current code? Commented Feb 21, 2013 at 1:37
  • Yes it should thanks for picking that up. However after making the correction to the declaration, it is exactly the same compile error as before. Commented Feb 21, 2013 at 1:44
  • @Ken: The XE3 IDE locates the error on the last line of the file (after the final end.) No line number is provided in the error message. Commented Feb 21, 2013 at 2:49

1 Answer 1

9

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.

Sign up to request clarification or add additional context in comments.

7 Comments

thanks a lot for the answer. It almost works, but the line: Result := nil causes a compile error Incompatible types: 'T' and 'Pointer'. That's why I previously type-cast this to a TIndexedObject. How do I set the Result for a generic type to nil or the generic equivalent?
In XE3 it works, but if your compiler requires a casting, use the Generic one: Result := T(nil);, as said in the answer.
Thanks for the advice, but unfortunately in Delphi 2010 the statement Result := T(nil); creates an Invalid typecast error. So neither approach seems to work in Delphi 2010. Interestingly if I comment out the line altogether it compiles without error. I'll test this further to see if it returns nil when an item is not in the list.
In Delphi 2010 (and maybe later?), the correct way to initialise a generic type for a function result is Result := Default(T);. That seems to works correctly for the function above.
@Rick and jachguate: thanks for Default(T). Learned something new.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.