Как установить предварительное объявление с универсальными типами в Delphi 2010?

Я сталкиваюсь с тем, что кажется очень классической проблемой: элемент и класс коллекции, ссылающиеся друг на друга, требуют предварительного объявления. Я использую Delphi 2010 с обновлением 5.

Это хорошо работает с не универсальными классами, но я не могу обойти ошибку E2086 с универсальными типами:

type
  // Forward declarations
  TMyElement = class; // E2086: Type 'TMyElement' is not yet completely defined

  TMyCollection<T:TMyElement> = class
    //
  end;

  TMyElement = class
    FParent: TMyCollection<TMyElement>;
  end;

Та же проблема возникает при переключении порядка объявления класса.

Я не нашел никакой ссылки на эту проблему здесь или в QualityCentral (были обнаружены другие проблемы с E2086, но не связанные с этим вариантом использования)

Единственный обходной путь, который у меня есть сейчас, - это объявить родительский объект как TObject и при необходимости привести его к универсальному типу коллекции (не чистое решение...)

Как вы обошли эту проблему или заранее объявили свои общие классы?

Спасибо,

[Редактировать 22 октября 2011 г.] Последующие действия в отношении QualityCentral: я сообщил об этой ошибке в центральном контроле качества здесь

Это было недавно закрыто EMB со следующим состоянием разрешения: Разрешение: Как спроектировано Решено в сборке: 16.0.4152

У меня только Delphi 2010. Может ли кто-нибудь подтвердить, что он был исправлен в Delphe XE2 Update1, или это означает, что он работает "как положено"?

[Редактировать 23 октября 2011 г.] Окончательный ответ от EMB: сегодня EMB подтвердил, что использование прямого объявления универсального типа не поддерживается фактическим компилятором Delphi. Вы можете увидеть их ответ в QC, по ссылке, указанной выше.

3 ответа

Решение

Вы можете обойти это, объявив класс предка:

type
  TBaseElement = class
  end;

  TMyCollection<T: TBaseElement> = class
  end;

  TMyElement = class(TBaseElement)
  private
    FParent: TMyCollection<TBaseElement>;
  end;

Похоже, Delphi уклоняется от пересылки классов, связанных с дженериками.

Вы также можете подумать о создании неуниверсального класса TMyCollectionBase, переместив туда весь код, который не зависит от типа T, возможно, добавив в него некоторые виртуальные функции, чтобы в идеале сделать все, что нужно, когда ссылается FParent. Я думаю о C++ здесь, но он может также уменьшить размер сгенерированного кода, когда TMyCollection используется для хранения элементов нескольких типов.

Пример моей коллекции (на основе дженериков)

type
  TMICustomItem = class(TPersistent)
  private
    FID: Variant;
    FCollection: TList<TMICustomItem>;
    function GetContained: Boolean;
  protected
    procedure SetID(Value: Integer);
  public
    constructor Create(ACollection: TList<TMICustomItem>); overload;
    constructor Create(ACollection: TList<TMICustomItem>; ID: Integer); overload;
    procedure Add; //Adding myself to parent collection
    procedure Remove; //Removing myself from parent collection        
    property Contained: Boolean read GetContained; //Check contains myself in parent collection
    property ID: Variant read FID;
  end;

  TMICustomCollection<ItemClass: TMICustomItem> = class(TList<ItemClass>)
  private
    function GetItemByID(ID: Integer): ItemClass;
  public
    property ItemID[ID: Integer]: ItemClass read GetItemByID; //find and return Item<ItemClass> in self by ID
  end;

...

{ TMICustomItem }

constructor TMICustomItem.Create(ACollection: TList<TMICustomItem>);
begin
  FCollection := ACollection;
end;

constructor TMICustomItem.Create(ACollection: TList<TMICustomItem>;
  ID: Integer);
begin
  Create(ACollection);
  FID := ID;
end;

procedure TMICustomItem.Add;
begin
  if not FCollection.Contains(Self) then
    FCollection.Add(Self)
  else
    raise EListError.CreateRes(@SGenericDuplicateItem);
end;

procedure TMICustomItem.Remove;
begin
  if FCollection.Contains(Self) then
    FCollection.Remove(Self)
  else
    raise EListError.CreateRes(@SGenericItemNotFound);
end;

function TMICustomItem.GetContained: Boolean;
begin
  Result := FCollection.Contains(Self);
end;

procedure TMICustomItem.SetID(Value: Integer);
begin
  FID := Value;
end;

{ TMICustomCollection<ItemClass> }

function TMICustomCollection<ItemClass>.GetItemByID(ID: Integer): ItemClass;
var
  I: Integer;
begin
  for I := 0 to Count - 1 do
    if Items[I].ID = ID then
      Exit(Items[I]);

  raise EListError.CreateRes(@SGenericItemNotFound);
end;
Другие вопросы по тегам