Search code examples
delphigenericsdelphi-xe6

How to define a template for generic collection item?


I'm trying to define my own items type for a generic list. All items need to have an owner property.

type 
  TGenericCollection<TGenericCollectionItem: class> = class(TObjectList<TGenericCollectionItem>)
  protected
    procedure Notify(const Value: TGenericCollectionItem; Action: TCollectionNotification); override;
  public
    procedure Assign(Source: TGenericCollection<TGenericCollectionItem>); virtual;
  end;

  TGenericCollectionItem = class
  private
    FOwner: TGenericCollection<TGenericCollectionItem>;  //another class can change this value if it was declared in the same unit
  protected
    function GetOwner: TGenericCollection<TGenericCollectionItem>; virtual;
  public
    constructor Create; virtual;
    property Owner: TGenericCollection<TGenericCollectionItem> read GetOwner;
  end;

implementation

{ TGenericCollection<TGenericCollectionItem> }
procedure TGenericCollection<TGenericCollectionItem>.Assign(
  Source: TGenericCollection<TGenericCollectionItem>);
begin
  AddRange(Source.ToArray);
end;

procedure TGenericCollection<TGenericCollectionItem>.Notify(
  const Value: TGenericCollectionItem; Action: TCollectionNotification);
begin
  inherited;

  if (Action = cnAdded) then
  begin
    OutputDebugString(PChar(Value.ClassName)); // Debug Output: THandingItem Process GenericCollection.exe (2424)
    OutputDebugString(PChar(Self.ClassName));  // Debug Output: THandingList Process GenericCollection.exe (2424)
//    Value.FOwner := Self;  // Error: [dcc32 Error] Unit2.pas(46): E2003 Undeclared identifier: 'FOwner'
//    (Value as TGenericCollectionItem).FOwner := Self;   // Error: [dcc32 Error] Unit2.pas(46): E2003 Undeclared identifier: 'FOwner'
//    (Value as TGenericCollectionItem<T>).FOwner := Self;   // Undeclared identifier 'T'
  end;
end;

When I'm trying to change FOwner value the compiler says that FOwner is undeclared identifier. THandingList inherits from TGenericCollection. THandingItem inherits from TGenericCollectionItem.

What I'm doing wrong? Sample project


UPD: TGenericCollectionItem changed in code sample.


Console app:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Generics.Collections;

type
  TGenericCollection<TGenericCollectionItem: class> = class(TObjectList<TGenericCollectionItem>)
  protected
    procedure Notify(const Value: TGenericCollectionItem; Action: TCollectionNotification); override;
  end;

  TGenericCollectionItem = class
  public
    Owner: TGenericCollection<TGenericCollectionItem>;
  end;

  procedure TGenericCollection<TGenericCollectionItem>.Notify(
    const Value: TGenericCollectionItem; Action: TCollectionNotification);
  begin
    inherited;

    if (Action = cnAdded) then
      Value.Owner := Self;
  end;

var
  GenericCollectionItem: TGenericCollectionItem;
  GenericCollection: TGenericCollection<TGenericCollectionItem>;
begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
    GenericCollection := TGenericCollection<TGenericCollectionItem>.Create;
    try
      GenericCollectionItem := TGenericCollectionItem.Create;
      GenericCollection.Add(GenericCollectionItem);
    finally
      GenericCollection.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Solution

  • Oh this is confusing. You've used TGenericCollectionItem as both a generic parameter name, and the name of a type. In TGenericCollection code the name TGenericCollectionItem refers to the generic type parameter rather than the class of the same name. And all that you know about TGenericCollectionItem the generic parameter is that it is a class. That is, it derives from TObject. And so has no member named Owner, or indeed FOwner.

    Change

    TGenericCollection<TGenericCollectionItem: class>
    

    to

    TGenericCollection<T: TGenericCollectionItem>
    

    Note that you will likely need to forward declare TGenericCollectionItem.

    Update: Argh, a forward declaration won't work when one of the mutually referential classes is generic, due to a design deficiency of Delphi generics. See How to set a forward declaration with generic types under Delphi 2010?