14
votes

I run into what seems to be a very classical problem: An item and a collection class, both referencing each other, that require a forward declaration. I'm using Delphi 2010 with update 5.

This works well with non generic classes but I can't workaround the E2086 error with generic types:

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

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

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

The same issue happens when switching the class declaration order.

I didn't found any reference to this issue here or in QualityCentral (other issues with E2086 were found, but not related to this use case)

The only workaround I have for now is to declare the parent as TObject, and cast it to the collection generic type when required (not a clean solution...)

How did you workaround this issue, or forward-declare your generic classes?

Thanks,

[Edit Oct 22, 2011] Follow up on QualityCentral: I reported this bug in quality central here

This has been closed recently by EMB with the following resolution status: Resolution: As designed Resolved in build: 16.0.4152

I only have Delphi 2010. Could someone confirm that it has been fixed in Delphe XE2 Update1, or does it mean that it works 'as expected'?

[Edit Oct 23, 2011] Final answer from EMB: EMB confirmed today that using forward declaration of a generic type is not supported by the actual Delphi compiler. You can see their answer in QC, with the link provided above.

3
+1 that should indeed work IMHOjpfollenius
This bug is still not fixed in XE6kot-da-vinci
@kot there is no bug to fixDavid Heffernan

3 Answers

13
votes

You can work around it by declaring an ancestor class:

type
  TBaseElement = class
  end;

  TMyCollection<T: TBaseElement> = class
  end;

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

Looks like Delphi shies away from forwarding classes related to generics.

You may also think about creating non generic TMyCollectionBase class by moving there all code that is not dependent on the T type maybe augmenting it with some virtual functions to ideally make it all what is needed when referred by FParent. I am thinking in C++ here but it might reduce as well the size of generated code when TMyCollection is used to store items of several types.

0
votes

My Collection's example (based on generics)

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;