Always Specify Inherited Interfaces

By | November 27, 2020

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.