Search code examples
delphiinterfacedelphi-xe7

Passing object which implements a child interface as its parent interface parameter


I have a parent interface (IParent), a child interface (IChild) and an object which implements the child interface.

I'm trying to call a function which accepts an array of IParent parameter by passing an array of objects implementing the child interface.

On compiling I get the following error:

[dcc32 Error] Unit1.pas(46): E2010 Incompatible types: 'IParent' and '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.

Solution

  • In order to assign a TForm1 object directly to an IParent, you have to include IParent in TForm1's declaration:

    TForm1 = class(TForm, IParent, IChild)
    

    This behavior is documented in Embarcadero's DocWiki:

    Implementing Interface References

    An interface-type expression cannot reference an object whose class implements a descendent interface, unless the class (or one that it inherits from) explicitly implements the ancestor interface as well.

    For example:

    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;
    

    In this example, A is declared as a variable of type IAncestor. Because TSomething does not list IAncestor among the interfaces it implements, a TSomething instance cannot be assigned to A. But if you changed TSomething's declaration to:

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

    the first error would become a valid assignment. D is declared as a variable of type IDescendant. While D references an instance of TSomething, you cannot use it to access TSomething's P2 method, since P2 is not a method of IDescendant. But if you changed D's declaration to:

    D: TSomething;
    

    the second error would become a valid method call.

    Alternatively, since IChild derives from IParent, you can explicitly cast the TForm1 object to IChild first, and then let the compiler convert the IChild into an IParent for you:

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

    This behavior is also documented:

    Interface Assignment Compatibility

    Variables of a given class type are assignment-compatible with any interface type implemented by the class. Variables of an interface type are assignment-compatible with any ancestor interface type. The value nil can be assigned to any interface-type variable.