I keep making this mistake even though I wrote about this briefly many years ago in the article IOTA Interfaces…. I’ve been bitten by this again while testing some new code in Browse and Doc It 1.3a Beta.
Specifically, I was not implementing all the inherited interfaces for an IOTAProjectNotifier
and as a consequence, I was not getting notified properly by the IDE when changes were being made. I decided to write a test project to prove to myself that this was the case.
The following code defines the test interfaces:
unit InterfaceUnit; interface type IMyInterface1 = Interface ['{DF0075EF-3337-457B-9AE4-8FF6193904A2}'] Procedure Method1; End; IMyInterface2 = Interface(IMyInterface1) ['{97AC07A4-2C51-435B-81AC-F764BF061796}'] Procedure Method2; End; IMyInterface3 = Interface(IMyInterface2) ['{CBB40562-778E-43BB-9941-E06CD8F40D28}'] Procedure Method3; End; IMyInterface4 = Interface(IMyInterface3) ['{CF53994E-BFC4-4639-86E3-A2953F8BB6A7}'] Procedure Method4; End; IMyInterface5 = Interface(IMyInterface4) ['{D4FB6929-B52C-44A4-B6B9-743CBF43A774}'] Procedure Method5; End; IMyInterface6 = Interface(IMyInterface5) ['{5868010D-E9F7-416E-B167-016DC75796A6}'] Procedure Method6; End; implementation end.
I then created the following implementations. The first implementation is as follows (I’m omitted the actual implementation using CodeSite but there is a zip file at the end of this post):
Type TMyImplementation3 = Class(TInterfacedObject, IMyInterface3) Strict Private Strict Protected Procedure Method1; Procedure Method2; Procedure Method3; Public Constructor Create; Destructor Destroy; Override; End;
This first implementation (#3) defines the third interface only and omits the first and second interface.
The second implementation is as follows:
Type TMyImplementation6 = Class(TInterfacedObject, IMyInterface6) Strict Private Strict Protected Procedure Method1; Procedure Method2; Procedure Method3; Procedure Method4; Procedure Method5; Procedure Method6; Public Constructor Create; Destructor Destroy; Override; End;
This implementation (#6) defines the sixth interface only and omits all the hierarchy.
The idea behind the above implementations is to prove that if you don’t define all the interfaces they may not be called in certain circumstances.
The third implementation (#123) is how the first implementation (#3) should have been defined and implements the first, second and third interfaces as follows:
Type TMyImplementation123 = Class(TInterfacedObject, IMyInterface1, IMyInterface2, IMyInterface3) Strict Private Strict Protected Procedure Method1; Procedure Method2; Procedure Method3; Public Constructor Create; Destructor Destroy; Override; End;
The fourth implementation (#456) is how the second implementation (#6) should have been defined and implements all the interfaces as follows:
Type TMyImplementation123456 = Class(TInterfacedObject, IMyInterface1, IMyInterface2, IMyInterface3, IMyInterface4, IMyInterface5, IMyInterface6) Strict Private Strict Protected Procedure Method1; Procedure Method2; Procedure Method3; Procedure Method4; Procedure Method5; Procedure Method6; Public Constructor Create; Destructor Destroy; Override; End;
These last two implementations are to show how you should implement interfaces that are inherited.
The below code tests the above and outputs to CodeSite. I’ll show the output afterwards.
C1 := TMyImplementation3.Create; C1.Method1; C1.Method2; C1.Method3; C2 := TMyImplementation6.Create; C2.Method4; C2.Method5; C2.Method6; CodeSite.Send(csmNote, 'C1.Interface1', Supports(C1, IMyInterface1, I)); CodeSite.Send(csmNote, 'C1.Interface2', Supports(C1, IMyInterface2, I)); CodeSite.Send(csmNote, 'C1.Interface3', Supports(C1, IMyInterface3, I)); CodeSite.Send(csmNote, 'C1.Interface4', Supports(C1, IMyInterface4, I)); CodeSite.Send(csmNote, 'C1.Interface5', Supports(C1, IMyInterface5, I)); CodeSite.Send(csmNote, 'C1.Interface6', Supports(C1, IMyInterface6, I)); CodeSite.Send(csmNote, 'C2.Interface1', Supports(C2, IMyInterface1, I)); CodeSite.Send(csmNote, 'C2.Interface2', Supports(C2, IMyInterface2, I)); CodeSite.Send(csmNote, 'C2.Interface3', Supports(C2, IMyInterface3, I)); CodeSite.Send(csmNote, 'C2.Interface4', Supports(C2, IMyInterface4, I)); CodeSite.Send(csmNote, 'C2.Interface5', Supports(C2, IMyInterface5, I)); CodeSite.Send(csmNote, 'C2.Interface6', Supports(C2, IMyInterface6, I)); C1 := TMyImplementation123.Create; C1.Method1; C1.Method2; C1.Method3; C2 := TMyImplementation123456.Create; C2.Method4; C2.Method5; C2.Method6; CodeSite.Send(csmNote, 'C1.Interface1', Supports(C1, IMyInterface1, I)); CodeSite.Send(csmNote, 'C1.Interface2', Supports(C1, IMyInterface2, I)); CodeSite.Send(csmNote, 'C1.Interface3', Supports(C1, IMyInterface3, I)); CodeSite.Send(csmNote, 'C1.Interface4', Supports(C1, IMyInterface4, I)); CodeSite.Send(csmNote, 'C1.Interface5', Supports(C1, IMyInterface5, I)); CodeSite.Send(csmNote, 'C1.Interface6', Supports(C1, IMyInterface6, I)); CodeSite.Send(csmNote, 'C2.Interface1', Supports(C2, IMyInterface1, I)); CodeSite.Send(csmNote, 'C2.Interface2', Supports(C2, IMyInterface2, I)); CodeSite.Send(csmNote, 'C2.Interface3', Supports(C2, IMyInterface3, I)); CodeSite.Send(csmNote, 'C2.Interface4', Supports(C2, IMyInterface4, I)); CodeSite.Send(csmNote, 'C2.Interface5', Supports(C2, IMyInterface5, I)); CodeSite.Send(csmNote, 'C2.Interface6', Supports(C2, IMyInterface6, I));
The output of the above is as follows and I’ll explain the results below.
TMyImplementation3.Create 0.58 ms - TMyImplementation3.Create TMyImplementation3.Create TMyImplementation3.Method1 0.34 ms - TMyImplementation3.Method1 TMyImplementation3.Method1 TMyImplementation3.Method2 0.19 ms - TMyImplementation3.Method2 TMyImplementation3.Method2 TMyImplementation3.Method3 0.54 ms - TMyImplementation3.Method3 TMyImplementation3.Method3 TMyImplementation6.Create 0.90 ms - TMyImplementation6.Create TMyImplementation6.Create TMyImplementation6.Method4 0.65 ms - TMyImplementation6.Method4 TMyImplementation6.Method4 TMyImplementation6.Method5 0.29 ms - TMyImplementation6.Method5 TMyImplementation6.Method5 TMyImplementation6.Method6 0.47 ms - TMyImplementation6.Method6 TMyImplementation6.Method6 C1.Interface1 = False C1.Interface2 = False C1.Interface3 = True C1.Interface4 = False C1.Interface5 = False C1.Interface6 = False C2.Interface1 = False C2.Interface2 = False C2.Interface3 = False C2.Interface4 = False C2.Interface5 = False C2.Interface6 = True TMyImplementation123.Create 0.18 ms - TMyImplementation123.Create TMyImplementation123.Create TMyImplementation3.Destroy 0.12 ms - TMyImplementation3.Destroy TMyImplementation3.Destroy TMyImplementation123.Method1 0.12 ms - TMyImplementation123.Method1 TMyImplementation123.Method1 TMyImplementation123.Method2 0.12 ms - TMyImplementation123.Method2 TMyImplementation123.Method2 TMyImplementation123.Method3 0.18 ms - TMyImplementation123.Method3 TMyImplementation123.Method3 TMyImplementation123456.Create 0.15 ms - TMyImplementation123456.Create TMyImplementation123456.Create TMyImplementation123456.Method4 0.15 ms - TMyImplementation123456.Method4 TMyImplementation123456.Method4 TMyImplementation123456.Method5 0.12 ms - TMyImplementation123456.Method5 TMyImplementation123456.Method5 TMyImplementation123456.Method6 0.21 ms - TMyImplementation123456.Method6 TMyImplementation123456.Method6 TMyImplementation6.Destroy 0.27 ms - TMyImplementation6.Destroy TMyImplementation6.Destroy C1.Interface1 = True C1.Interface2 = True C1.Interface3 = True C1.Interface4 = False C1.Interface5 = False C1.Interface6 = False C2.Interface1 = True C2.Interface2 = True C2.Interface3 = True C2.Interface4 = True C2.Interface5 = True C2.Interface6 = True TMyImplementation123.Destroy 0.65 ms - TMyImplementation123.Destroy TMyImplementation123.Destroy TMyImplementation123456.Destroy 0.43 ms - TMyImplementation123456.Destroy
What you should see from the above is that if you define the first two implementation using the relevant interfaces (C1 := TMyInterface3.Create
) then you have access to all the inherited interfaces however if the calling code is specifically looking for an implementation of an interface, then the implementation does not recognise the inherited interfaces (C1.Interface2 = False
).
If this is your application, then you are more likely to find this issue in your unit tests or in seeing that your application doesn’t behave as expected however when using similar code in the IDE, you have no idea how the code is called and as a consequence parts of the interface may not be called.
I hope this help some of you when you come of try implementing interface inheritance and I hope that by doing this, I’ll not forget this issue any time soon.
The files for the above are attached below.
regards
Dave.