Почему я не могу передать TObjectList‹S: T› функции, ожидающей TObjectList‹T›?

У меня проблема с моим кодом, который использует универсальные типы. Почему компилятор не знает, что переданный список (Result) является TObjectList<TItem> (TItem является типом для T в TItems)?

Интерфейс:

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;

Реализация:

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;

person robertw    schedule 27.04.2012    source источник


Ответы (2)


В функции с ошибкой Result — это TObjectList<T>, где T — это какой-то подкласс TItem, но компилятор не знает, что это за класс. Компилятор должен скомпилировать его так, чтобы его можно было безопасно запускать для любого значения T. Это может быть несовместимо с типом аргумента LoadAll, для которого требуется TObjectList<TItem>, поэтому компилятор отклоняет код.

Предположим, что T равно TItemDescendant, и компилятор позволяет скомпилировать и выполнить ошибочный код. Если LoadAll позвонит AList.Add(TItem.Create), то AList в конечном итоге получит что-то, что не является TItemDescendant, даже если это TObjectList<TItemDescendant>. Он содержит объект типа, отличного от того, о чем говорит его параметр универсального типа.

Тот факт, что S является подтипом T, не означает, что X<S> является подтипом X<T>.

person Rob Kennedy    schedule 27.04.2012
comment
Вы правы, я не смотрел на это с этой стороны. Есть ли у вас какие-либо предложения, как реорганизовать этот код? Для меня важно иметь такой интерфейс, как LoadAll, и возвращать список результатов или передавать список результатов в качестве параметра в TItems.LoadAll. Но реальную работу выполняет TItemsLoader. TItems - это только косвенный слой. TItems знает, какие предметы будут у вас. TItemsLoader должен знать только, что элементы являются TItem или его потомком. - person robertw; 27.04.2012
comment
Я считаю, что другой ответ с интерфейсом IItemsLoader - это то, что вам нужно изменить, чтобы сделать. Однако Роб точно объяснил это правильно и ответил на ваш прямой вопрос. - person Warren P; 28.04.2012

Вы также должны использовать общую версию загрузчика:

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;
person Jouni Aro    schedule 27.04.2012
comment
В моей концепции загрузчик должен работать только на TItem. Но я постараюсь изменить его. - person robertw; 28.04.2012
comment
Он будет работать с TItem, а также с любым другим подклассом, поскольку универсальное определение имеет ограничение. - person Jouni Aro; 28.04.2012