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.
b := TMyObj.Create;
give an incompatible type error?Supports(i, IMyBase, b)
returns false
?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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With