Delphi allows for interface delegation using the implements
keyword.
For example
IIndep1 = interface
function foo2: integer;
end;
IIndep2 = interface
function goo2: integer;
end;
TIndep1And2 = class(TInterfacedObject, IIndep1, IIndep2)
private
FNested : IIndep1; //e.g. passed via constructor or internally created (not shown here)
public
Constructor Create(AIndep1: IIndep1);
function goo2: integer;
property AsIndep1 : IIndep1 read FNested implements IIndep1;
end;
That works well, but not for inherited interfaces. (Error message "Missing implementation of interface method ILev1.foo")
ILev1 = interface
function foo: Integer;
end;
ILev2 = interface(ILev1)
function goo: Integer;
end;
TLev2Fails = class(TInterfacedObject, ILev1, ILev2) //Also fails with ILev2 alone (Error: "ILev1 not mentioned in interface list")
private
FNested : ILev1; //passed via constructor or internally created
public
Constructor Create(AILev1: ILev1);
function goo: Integer;
property AsLev1 : ILev1 read FNested implements ILev1;
end;
The workaround is to add an extra ancestor class
TLev1Wrapper = class(TInterfacedObject, ILev1)
private
FNested : ILev1; //passed via constructor or internally created
public
Constructor Create(AILev1: ILev1);
property AsLev1 : ILev1 read FNested implements ILev1;
end;
TLev2Works = class(TLev1Wrapper, ILev2)
public
function goo: Integer;
end;
Is there a way to avoid the wrapper class ancestor?
[EDIT]
Just a note on interface delegation, the purpose of using implements
is to avoid satisfying the interface directly, but passing that requirement to an aggregated or composed member. Providing the full interface and manually delegating to a composed member defeats the benefits gained from using implements
to direct the interface. In fact, in that case the implements
keyword and property may be removed.
This looks like the compiler is attempting to enforce the expectations (read: requirements) of IUnknown.QueryInterface:
For any one object, a specific query for the IUnknown interface on any
of the object's interfaces must always return the same pointer value.
If you were able to delegate the implementation of a base interface whilst implementing a derived interface yourself then:
obj := TLev2Fails.Create(otherLev1); // Assuming your class could compile
lev1 := obj as ILev1; // yields reference to otherLev1 implementor
lev2 := obj as ILev2; // yields reference to TLev2Fails instance
unk1 := lev1 as IUnknown; // returns IUnknown of otherLev1 implementor
unk2 := lev2 as IUnknown; // returns IUnknown of obj TLev2fails instance
This would not be the case if your nested object were correctly implemented as a TAggregatedObject derived class, but the compiler has no way of knowing whether this is the case let alone enforcing it so instead it appears it simply requires that if you implement a derived interface then you must also directly implement any interfaces that interface itself inherits.
The compiler error in this situation isn't being very helpful, though it could be read as telling you what you need to do, just not why you need to do it in this case, which is not that unusual for compiler errors.
If you wish to delegate in this case then you must "delegate manually".
Whilst this loses the benefit of the implements
facility, it does at least retain the benefits of re-use, just not quite as conveniently.
NOTE: Even if your delegated implementation is based on a TAggregatedObject, the compiler still cannot determine that these implementation details satisfies the requirements of QueryInterface so you will still get this error (even if using a class reference for the delegated interface).
You must still delegate manually.
Having said all that, I cannot currently see how this is any different for the case when interfaces are involved with no inheritance relationship, but it is quite possible that this is valid and I just haven't worked through all the necessary 'thought experiments' to prove it to myself. :)
It may be that the compiler is just being extra cautious in situations when it thinks it can/should be and the documentation simply fails to mention this resulting limitation of implements
.
Or it may be a bug in the compiler, though I think there are sufficiently apparent reasons for the behaviour and the behaviour is itself so well established and consistent (having reproduced the exact same behaviour in Delphi 7) that an omission in relevant documentation is the more likely explanation.
Is there a way to avoid the wrapper class ancestor?
No, the inherited interface has to be bound to a an ancestor class. Delphi does not implicitly bind inherited interfaces, because of reasons explained here. Also, it cannot bind to the current class, unless it manually delegates all calls to the composed item.
We are left with only one option. An ancestor wrapper class, to which we can bind ILev1