Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my class implement child interfaces, but not their parents?

I found a (at least for me) unexpected behavior when using interface inheritance in Delphi.

I have this simple class and interface hierarchy:

+---------------+
| << IMyBase >> |
+---------------+
        ^
        |
+---------------+
| << IMyIntf >> |
+---------------+
        ^
        |
   +---------+
   | TMyObj  |
   +---------+

I wanted to declare a variable of type IMyBase. Create a TMyObj and assign it to my variable. IHMO this is normal OOP practice. But it turned out that it does not compile.

I Have also tried to declare a variable of type IMyIntf and check if it supports IMyBase, IMHO it should support it, but it doesn't.

Here is a simple test code:

program interface_inheritance;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  IMyBase = interface
    ['{CC7C61B8-3FBA-481F-AF0D-A93C603B5202}']
    procedure Hello;
  end;

  IMyIntf = interface(IMyBase)
    ['{01CE01D9-A753-431C-A30E-64BAEC6C4E26}']
    //
  end;

  TMyObj = class(TInterfacedObject, IMyIntf)
    procedure Hello;
  end;

{ TMyObj }

procedure TMyObj.Hello;
begin
  Writeln('Hello World');
end;

var
  b: IMyBase;
  i: IMyIntf;
begin
(*  // Compile Error E2010
  b := TMyObj.Create;
  b.Hello;*)

  // Does not work as Expected
  // Does not call Hello()
  i := TMyObj.Create;
  if Supports(i, IMyBase, b) then begin
    // Why does i not support IMyBase ??
    b.Hello;
  end;

  // Works but unsafe!
  // Hard cast, without check.
  i := TMyObj.Create;
  b := IMyBase(i);
  b.Hello;

  // Works, of course!
  i := TMyObj.Create;
  i.Hello;

  Readln;
end.

As you can see i have a valid class/interface structure. but some parts do not compile. and some do not execute as expected.

  1. Why does b := TMyObj.Create; give an incompatible type error?
  2. Why does Supports(i, IMyBase, b) returns false?
  3. Is there another (better) way to solve such a problem? without a hard cast without check? (if i is IMyBase does not work, because interfaces do not support is operator.)

Is this valid Pascal/Delphi behavior or a bug? IMHO Supports() should return true. and TMyObj should be a valid IMyBase (and therefor be assignable).

like image 587
linluk Avatar asked May 15 '18 07:05

linluk


1 Answers

This might seem a little counter intuitive, but your class must declare that it implements the parent interface, too. Your class declaration must be like so:

TMyObj = class(TInterfacedObject, IMyBase, IMyIntf)

Danny Thorpe, a former Borland engineer, explained the rationale behind this behaviour in an answer to a related question:

If an implementing class does not declare that it supports an inherited interface, then the class will not be assignment compatible with variables of the inherited interface. The code sample you posted should work fine (using the IChild interface), but if you try to assign from an instance of TMyClass to a variable of IParent, then you'll run into trouble.

The reason is because COM and ActiveX allow an implementation to implement a descendent interface (your IChild) but deny the ancestor of that interface (IParent). Since Delphi interfaces are intended to be COM compatible, that's where this goofy artifact comes from.

like image 102
David Heffernan Avatar answered Nov 09 '22 22:11

David Heffernan