The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 4,262 other subscribers

Delphi: not all lists need to be generic

Posted by jpluimers on 2021/02/18

Lots of Delphi programmers made, or are making the move, of classic Delphi based containers like TObjectList into generic containers like TList<T>.

A while ago, I got into a project that needed to extend lifetime of some objects. Virtually all of them were interface based, and most of the code was from the non-Unicode era, and most of the developers there had a strong background in that era, so they started fiddling with TList, found it hard, then thought “maybe TList<IInterface>” where will help.

The problem however, is that Delphi has no IList<T>. For that, you have to go to the Spring4D library.

Then I sat down with them, and proposed to use an instance good old TInterfacedList of which the context was maintained in an IInterfacedList field.

Back in the days where Delphi did not support non-generic types, TInterfacedList was the only built-in way to store interface references, and the Collection Classes framework by Ray Lischner were the only ways to do that in a more structured way (as they were based on interfaces, an idiom that Embarcadero should have used for their generic collections as well; Spring4D did, so use those collection classes and interfaces whenever possible as they are way more versatile than the Delphi built-in ones)

Back to using TInferfacedList, as it can still be useful today in:

unit InterfacesHolderUnit;

interface

uses
  DebuggableInterfacedObjectUnit, System.Classes;

type
  IInterfacesHolder = interface
    procedure Add(const aReference: IInterface);
  end;

  TInterfacesHolder = class(TInterfacedObject, IInterfacesHolder)
  strict private
    FInterfaces: IInterfaceList;
  public
    constructor Create();
    procedure Add(const aReference: IInterface);
  end;

implementation

{ TInterfacesHolder }

procedure TInterfacesHolder.Add(const aReference: IInterface);
begin
   FInterfaces.Add(aReference);
end;

constructor TInterfacesHolder.Create();
begin
   inherited Create();
   FInterfaces := TInterfaceList.Create();
end;

end.

and some tests:

unit InterfacesHolderTestCaseUnit;

interface

uses
  TestFramework;

type
  TInterfacesHolderTestCase = class(TTestCase)
  published
    procedure TInterfacesHolder_Keeps_Instance_Alive();
    procedure TInterfacesHolder_When_Destroyed_Releases_Interfaces();
  end;

implementation

uses
  System.SysUtils,
  TestCaseHelperUnit,
  InterfacesHolderUnit;

type
  TDestroyNotifyingInterfacedObject = class(System.TInterfacedObject)
  public
    type
      TNofitier = TProc;
  strict private
    FDestroyNotifier: TNofitier;
  public
    constructor Create(const ADestroyNotifier: TNofitier);
    destructor Destroy(); override;
  end;

constructor TDestroyNotifyingInterfacedObject.Create(const ADestroyNotifier: TNofitier);
begin
  inherited Create();
  FDestroyNotifier := ADestroyNotifier;
end;

destructor TDestroyNotifyingInterfacedObject.Destroy();
begin
  inherited Destroy();
  if Assigned(FDestroyNotifier) then
    FDestroyNotifier(Self);
end;

procedure TInterfacesHolderTestCase.TInterfacesHolder_Keeps_Instance_Alive();
var
  Owner:               IInterfacesHolder;
  Instance:            TDestroyNotifyingInterfacedObject;
  InstanceReference:   IInterface;
  IsInstanceDestroyed: Boolean;
begin
  Owner               := TInterfacesHolder.Create();
  IsInstanceDestroyed := False;
  Instance            := TDestroyNotifyingInterfacedObject.Create(
    procedure(aInstance: TDestroyNotifyingInterfacedObject)
    begin
      if Instance = aInstance then
        IsInstanceDestroyed := True;
    end);
  InstanceReference := Instance;
  Owner.Add(Instance);
  InstanceReference := nil;
  CheckFalse(IsInstanceDestroyed, 'Instance should still be alive because Owner keeps a reference');
end;

procedure TInterfacesHolderTestCase.TInterfacesHolder_When_Destroyed_Releases_Interfaces();
var
  Owner:               IInterfacesHolder;
  Instance:            TDestroyNotifyingInterfacedObject;
  IsInstanceDestroyed: Boolean;
begin
  Owner               := TInterfacesHolder.Create();
  IsInstanceDestroyed := False;
  Instance            := TDestroyNotifyingInterfacedObject.Create(
    procedure(aInstance: TDestroyNotifyingInterfacedObject)
    begin
      if Instance = aInstance then
        IsInstanceDestroyed := True;
    end);
  Owner.Add(Instance);
  Owner := nil;
  CheckTrue(IsInstanceDestroyed, 'Owner should release last reference to Instance so it gets destroyed');
end;

initialization

RegisterTest(TInterfacesHolderTestCase.Suite);

end.

Related:

–jeroen

One Response to “Delphi: not all lists need to be generic”

  1. lhengen said

    TNofitier should be TNotifier

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.