TGenericClass, содержащий TObjectList, не компилируется


Я пытаюсь написать универсальный класс, который содержит универсальный TObjectList, который должен содержать только элементы 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;

Это неправильный синтаксис? Кстати: tgenericclass компилируется, но тогда элементы в списке больше не являются TItem, чего я не хочу.

3 3

3 ответа:

Это известная ошибка с компилятором D2009. Скорее всего, он будет исправлен в ближайшее время, либо в обновлении или исправлении для 2009 года, либо в Delphi 2010 (Weaver), как только он будет выпущен. А до тех пор, к сожалению, вам нужен какой-то обходной путь. : (

Универсальные типы могут иметь несколько ограничений:

  • имя класса, общий тип должен принадлежать этому классу потомка класса.
  • имя интерфейса, универсальный тип должен реализовать этот интерфейс.
  • 'class', универсальный тип должен быть классом (это не может быть объединено с именем класса).
  • 'record', универсальный тип должен быть записью.
  • 'конструктор', немного расплывчатый, но вы можете создавать экземпляры универсального типа класса.

Если вы создайте дженерик, который использует другие дженерики, вам нужно скопировать ограничения, иначе он не будет работать. В вашем случае tobjectlist имеет ограничение класса. Это означает, что ваше Т тоже нуждается в этом ограничении.

К сожалению, это не может быть объединено с ограничением именованного класса.

Поэтому я советую вам использовать интерфейс, они могут быть объединены:

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

Кроме того, вы должны сделать свои поля закрытыми, иначе их может изменить кто угодно.

Мне нравится ответ GameCat (дал его +1) для описания ограничений класса.

У меня есть небольшая модификация вашего кода, которая работает. Обратите внимание, что поскольку вы дали ограничение, чтобы сказать, что T должен быть потомком TItem, вы можете просто объявить ObjectList как TObjectList<TItem> - нет необходимости использовать T здесь.

В качестве альтернативы можно создать своего рода прокси. Во-первых, обратите внимание на комментарий GameCat о том, что поля являются частными.

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 является примером того, как получить доступ к список объектов. Как оказалось, Вы можете передать элемент в ObjectList.Добавьте без малейших затруднений:

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

Я думаю, что это может быть ошибка, поэтому, чтобы защитить себя от того, что исправлено:

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

Однако, учитывая ваш сценарий, я бы сказал, что TObjectList должен делать все отлично.