Передача объекта, который реализует дочерний интерфейс в качестве параметра родительского интерфейса - PullRequest
1 голос
/ 16 апреля 2020

У меня есть родительский интерфейс (IParent), дочерний интерфейс (IChild) и объект, который реализует дочерний интерфейс.

Я пытаюсь вызвать функцию, которая принимает array of IParent параметр путем передачи массива объектов, реализующих дочерний интерфейс.

При компиляции я получаю следующую ошибку:

[Ошибка dcc32] Unit1.pas (46): E2010 Несовместимые типы : 'IParent' и 'TForm1'

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs;

type
  IParent = interface
    procedure DoSomething();
  end;

  IChild = interface(IParent)
    procedure DoSomethingElse();
  end;

  TForm1 = class(TForm, IChild)
    procedure FormCreate(Sender: TObject);
  public
    procedure DoSomething();
    procedure DoSomethingElse();
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure CallAllDoSomething(AArray : array of IParent);
var
  i : integer;
begin
  i := 0;
  while(i < Length(AArray)) do
  begin
    AArray[i].DoSomething();
    Inc(i);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Unit1.CallAllDoSomething([Self]);
end;

procedure TForm1.DoSomething();
begin
  ShowMessage('Something');
end;

procedure TForm1.DoSomethingElse();
begin
  ShowMessage('Something else');
end;

end.

Ответы [ 2 ]

4 голосов
/ 16 апреля 2020

Вы также должны добавить IParent к объявлению TForm1:

 TForm1 = class(TForm, IParent, IChild)
3 голосов
/ 16 апреля 2020

Чтобы назначить TForm1 объект напрямую для IParent, необходимо включить IParent в объявление TForm1:

TForm1 = class(TForm, IParent, IChild)

Это поведение задокументировано в DocWiki Embarcadero:

Реализация ссылок на интерфейсы

Выражение типа интерфейса не может ссылаться на объект, класс которого реализует интерфейс потомка, если только класс (или тот, от которого он наследует) также явно реализует интерфейс предка.

Например:

type
  IAncestor = interface
  end;
  IDescendant = interface(IAncestor)
    procedure P1;
  end;
  TSomething = class(TInterfacedObject, IDescendant)
    procedure P1;
    procedure P2;
  end;
     // ...
var
  D: IDescendant;
  A: IAncestor;
begin
  D := TSomething.Create;  // works!
  A := TSomething.Create;  // error
  D.P1;  // works!
  D.P2;  // error
end;

В этом примере A объявлено как переменная типа IAncestor. Поскольку TSomething не перечисляет IAncestor среди интерфейсов, которые он реализует, экземпляр TSomething не может быть назначен A. Но если вы изменили объявление TSomething на:

TSomething = class(TInterfacedObject, IAncestor, IDescendant)
// ...

, первая ошибка станет допустимым назначением. D объявлен как переменная типа IDescendant. Хотя D ссылается на экземпляр TSomething, вы не можете использовать его для доступа к методу TS2 Pomething, поскольку P2 не является методом IDescendant. Но если вы измените объявление D на:

D: TSomething;

, вторая ошибка станет допустимым вызовом метода.

В качестве альтернативы, поскольку IChild происходит от IParent, вы можете явно привести объект TForm1 к IChild, а затем позволить компилятору преобразовать IChild в IParent для вас:

Unit1.CallAllDoSomething([IChild(Self)]);

Это поведение также задокументировано:

Совместимость назначения интерфейса

Переменные данного типа класса совместимы по присваиванию с любым типом интерфейса, реализованным классом. Переменные типа интерфейса совместимы по присваиванию с любым типом интерфейса предка. Значение nil может быть присвоено любой переменной типа интерфейса.

...