views:

548

answers:

3

Hi, I'm trying to write a generic class which contains a generic TObjectList< T > which should contain only Elements of TItem.

uses
  Generics.Collections;

type
  TItem = class
  end;

  TGenericClass<T: TItem> = class
  public
    SimpleList: TList<T>; // This compiles
    ObjectList: TObjectList<T>; // This doesn't compile: Compiler complaints that "T is not a class type"
  end;

Is this a wrong syntax? BTW: TGenericClass< T: class> compiles, but then the Items in the List are not TItem anymore which is what I don't want.

+6  A: 

Generic types can have several constraints:

  • A class name, the generic type must be of that class of a descendant of the class.
  • An interface name, the generic type must implement that interface.
  • 'class', the generic type must be a class (this can't be combined with a classname).
  • 'record', the generic type must be a record.
  • 'constructor', a bit vague, but you can create instances of the generic class type.

If you create a generic that uses other generics, you need to copy the constraints, else it won't work. In your case, TObjectList has the class constraint. This means, your T needs that constraint too.

Unfortunately this can't be combined with the named class constraint.

So I advice you to use an interface, these can be combined:

type
  IItem = interface end;
  TItem = class (TInterfacedObject, IItem) end;
  TGenericClass<T: class, IItem> = class
  private
    FSimpleList: TList<T>;
    FObjectList: TObjectList<T>;
  end;

Besides, you should make your fields private else anyone can change them.

Gamecat
+6  A: 

This is a known bug with the D2009 compiler. It will most likely be fixed soon, either in an update or hotfix for 2009, or in Delphi 2010 (Weaver) once it gets released. Until then, you need some sort of workaround, unfortunately. :(

Mason Wheeler
+3  A: 

I like GameCat's answer (gave it +1) for the description of class constraints.

I have a slight modification of your code that works. Note that since you gave a constraint to say that T must be a descendant of TItem, you can actually just declare ObjectList as TObjectList<TItem> - no need to use T here.

Alternatively, you could create a proxy of sorts. First, note GameCat's comment about fields being private.

type
  TGenericClass<T: TItem> = class
  private
    type
      D = class(TItem); // Proxy to get your T into and object list
  private
    SimpleList: TList<T>;
    ObjectList: TObjectList<D>; // Compiles now, but there is that type issue
  public
    procedure Add(Item: T); // No direct access to ObjectList
  end;

Add is an example of how to access the object list. As it turns out, you can pass Item to ObjectList.Add with no trouble whatsoever:

procedure TGenericClass<T>.Add(Item: T);
begin
  ObjectList.Add(Item);
end;

I think that may be a bug though, so to protect yourself against that getting fixed:

procedure TGenericClass<T>.Add(Item: T);
var
  Obj: TObject;
begin
  Obj := Item;
  ObjectList.Add(D(Obj));
end;

Given your scenario though, I'd say TObjectList should do just fine.

Cobus Kruger
Yeah, this'll work, but it's exactly the sort of ugly code that generic lists were introduced to free us from. This is a compiler bug, pure and simple. It's been acknowledged as one by the team, and it'll be fixed soon. A workaround like this will work, for the moment, but don't get too attached to it.
Mason Wheeler
Yes, agreed. The compiler bug is likely to be fixed only after Stebi fixes his code though :-) Also, I did say the best way is to just pass TItem as type for the ObjectList declaration - that way it is not ugly, works well and offer no drawbacks I can think of.
Cobus Kruger
You are right, if I avoid giving the objectlist to the public than everything is solveable. But then I it would be enough to use a normal objectlist and cast the item to the desired type T. Ok, casting is not as easy (compilerbug too?), but solveable like here: http://stackoverflow.com/questions/580108/delphi-generic-constraints-problem
Stebi
Except of course that if you use regular TObjectList, you'll need to cast to and from TObject. If you use TObjectList<TItem>, you won't need to do any casting whatsoever.About the compiler bug - just because it is open does not mean it will be fixed soon. QualityCentral is full of opened bugs that have been like that for years.
Cobus Kruger