Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I pass a TObjectList<S: T> to a function expecting a TObjectList<T>?

I have problem with my code, which uses generic types. Why doesn't the compiler know that the passed list (Result) is a TObjectList<TItem> (TItem is type for T in TItems)?

Interface:

type
   TItem = class
end;

type
  IItemsLoader = interface
    procedure LoadAll(AList : TObjectList<TItem>);
end;

type
  TItemsLoader = class(TInterfacedObject, IItemsLoader)
public
  procedure LoadAll(AList : TObjectList<TItem>);
end;

type
  IItems<T : TItem> = interface
  function LoadAll : TObjectList<T>;
end;

type
  TItems<T : TItem> = class(TInterfacedObject, IItems<T>)
  private
    FItemsLoader : TItemsLoader;
  public
    constructor Create;
    destructor Destroy; override;
    function LoadAll : TObjectList<T>;
end;

Implementation:

procedure TItemsLoader.LoadAll(AList: TObjectList<TItem>);
begin
  /// some stuff with AList
end;

{ TItems<T> }

constructor TItems<T>.Create;
begin
  FItemsLoader := TItemsLoader.Create;
end;

destructor TItems<T>.Destroy;
begin
  FItemsLoader.Free;
  inherited;
end;

function TItems<T>.LoadAll: TObjectList<T>;
begin
  Result := TObjectList<T>.Create();

  /// Error here
  /// FItemsLoader.LoadAll(Result);
end;
like image 440
robertw Avatar asked Apr 27 '12 16:04

robertw


2 Answers

You have to use a generic version of the Loader as well:

type
   TItem = class
end;

type
  IItemsLoader<T: TItem> = interface
    procedure LoadAll(AList : TObjectList<T>);
end;

type
  TItemsLoader<T: TItem> = class(TInterfacedObject, IItemsLoader<T>)
public
  procedure LoadAll(AList : TObjectList<T>);
end;

type
  IItems<T : TItem> = interface
  function LoadAll : TObjectList<T>;
end;

type
  TItems<T : TItem> = class(TInterfacedObject, IItems<T>)
  private
    FItemsLoader : TItemsLoader<T>;
  public
    constructor Create;
    destructor Destroy; override;
    function LoadAll : TObjectList<T>;
end;


implementation

{$R *.dfm}

procedure TItemsLoader<T>.LoadAll(AList: TObjectList<T>);
begin
  /// some stuff with AList
end;

{ TItems<T> }

constructor TItems<T>.Create;
begin
  FItemsLoader := TItemsLoader<T>.Create;
end;

destructor TItems<T>.Destroy;
begin
  FItemsLoader.Free;
  inherited;
end;

function TItems<T>.LoadAll: TObjectList<T>;
begin
  Result := TObjectList<T>.Create();

  /// Error here
  FItemsLoader.LoadAll(Result);
end;
like image 198
Jouni Aro Avatar answered Nov 16 '22 03:11

Jouni Aro


In the function with the error, Result is a TObjectList<T>, where T is some subclass of TItem, but the compiler doesn't know what specific class it is. The compiler has to compile it so that it's safe to run for any value of T. That might not be compatible with the argument type of LoadAll, which requires a TObjectList<TItem>, so the compiler rejects the code.

Suppose T is TItemDescendant, and the compiler allows the faulty code to compile and execute. If LoadAll calls AList.Add(TItem.Create), then AList will end up holding something that isn't a TItemDescendant, even though it's a TObjectList<TItemDescendant>. It holds an object of a type different from what its generic type parameter says it holds.

Just because S is a subtype of T doesn't mean that X<S> is a subtype of X<T>.

like image 43
Rob Kennedy Avatar answered Nov 16 '22 02:11

Rob Kennedy