Jump to content
Dmitry Onoshko

Generic container and pointer to it in its elements

Recommended Posts

Consider a container:

type
  TCustomItem = class
  protected
    FContainer: // ???
  end;
  
  TCustomContainer<T: TCustomItem> = class abstract
  protected
    FItems: TArray<T>;
    ...
    procedure HandleItemNotification(AItem: T);
  end;

The items are supposed to be classes derived from common ancestor in parallel with containers:

type
  TFooItem = class(TCustomItem);
  TFooContainer = class(TCustomContainer<TFooItem>);
  
  TBarItem = class(TCustomItem);
  TBarContainer = class(TCustomContainer<TBarItem>);

An item should store a pointer to its container for notification purposes. But TFooItem should never be used with TBarContainer or vice versa.

 

I seem to get closer when I declare the TCustomItem as inner class:

type
 
  TCustomContainer<T: TCustomItem> = class abstract
  public type
    TCustomItem = class
    protected
      FContainer: TCustomContainer<T>;
    end;
  protected
    FItems: TArray<T>;
    ...
    procedure HandleItemNotification(AItem: T);
  end;

But then, when I call

procedure TCustomContainer<T>.TCustomItem.SomeMethod;
begin
  ...
  FContainer.HandleItemNotification(Self);
  ,,,
end;

inside TCustomItem method, I get “Incompatible types: T and UnitName.TCustomContainer<T>.TCustomItem”.

 

Is this even possible?

Share this post


Link to post
12 minutes ago, Dmitry Onoshko said:

 


type
 
  TCustomContainer<T: TCustomItem> = class abstract
  public type
    TCustomItem = class
    protected
      FContainer: TCustomContainer<T>;
    end;
  protected
    FItems: TArray<T>;
    ...
    procedure HandleItemNotification(AItem: T);
  end;

But then, when I call


procedure TCustomContainer<T>.TCustomItem.SomeMethod;
begin
  ...
  FContainer.HandleItemNotification(Self);
  ,,,
end;

inside TCustomItem method, I get “Incompatible types: T and UnitName.TCustomContainer<T>.TCustomItem”.

It is not possible to define TCustomContainer<T: TCustomItem> if TCustomItem is its internal public type. This only compiles (up to your error) because there is another TCustomItem defined outside that class.

 

Doing this with classes only is impossible. You cannot have both a T that is a TCustomItem within TCustomContainer<T> and a FContainer that is a TCustomContainer<T> within TCustomItem. The relation cannot be bi-directional in this scenario. A closer implementation to what you're trying to achieve is possible with interfaces, but that comes with its own set of issues. This post has an example of what you're trying to achieve with interfaces: 

 

Also, IMO it is not a good idea to couple your classes like you're trying to do here. A TCustomItem should not directly call the TCustomContainer's HandleItemNotification; you should handle that either via an observer pattern or with events.

  • Like 1

Share this post


Link to post
type
  TCustomItem = class;
  TCustomContainer = class abstract
  protected
    procedure HandleItemNotification(AItem: TCustomItem); virtual; abstract;
  end;

  TCustomItem = class
  protected
    FContainer: TCustomContainer;
  end;

  TCustomContainer<T: TCustomItem> = class abstract(TCustomContainer)
  protected
    FItems: TArray<T>;
    procedure HandleItemNotification(AItem: TCustomItem); overload; override; final;
    procedure HandleItemNotification(AItem: T); reintroduce; overload;
  end;

  TFooItem = class(TCustomItem)
    procedure SomeMethod;
  end;
  TFooContainer = class(TCustomContainer<TFooItem>);


procedure TCustomContainer<T>.HandleItemNotification(AItem: T);
begin
  // ...
end;

procedure TCustomContainer<T>.HandleItemNotification(AItem: TCustomItem);
begin
  HandleItemNotification(T(AItem));
end;

procedure TFooItem.SomeMethod;
begin
  FContainer.HandleItemNotification(Self);
end;

var
  foo: TFooItem;
begin
  foo := TFooItem.Create;
  foo.FContainer := TFooContainer.Create;
  foo.SomeMethod;
end.

I suggest moving as much code into the non generic base classes as possible. Given the constraint on TCustomItem this should even be reasonably easy to achieve. This will prevent causing larger than necessary binary sizes and longer than necessary compiletimes.

Edited by Stefan Glienke

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×