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)
- Classic
IInterfacedList
example: - Collection Classes articles:
- [WayBack] Interfaces and Collection Classes in Delphi | Dr Dobb’s – 1
- [WayBack] Interfaces and Collection Classes in Delphi | Dr Dobb’s – 2
- [WayBack] Interfaces and Collection Classes in Delphi | Dr Dobb’s – 3
- [WayBack] Interfaces and Collection Classes in Delphi | Dr Dobb’s – 4
- [WayBack] Interfaces and Collection Classes in Delphi | Dr Dobb’s – 5
- [WayBack] Interfaces and Collection Classes in Delphi | Dr Dobb’s – 6
- [WayBack] Interfaces and Collection Classes in Delphi | Dr Dobb’s – 7
- [WayBack] Interfaces and Collection Classes in Delphi | Dr Dobb’s – 8
- [WayBack] Interfaces and Collection Classes in Delphi | Dr Dobb’s – 9
- [WayBack] Interfaces and Collection Classes in Delphi | Dr Dobb’s – 10
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:
- [WayBack] TObjectList Class
- [WayBack] TInterfaceList Class
- [WayBack] IInterfaceList Interface
- [WayBack] TList Class
- [Archive.is] Spring4D: Spring.Collections.IList<T>
- [Archive.is] Spring4D: Spring.Collections.Lists.TList<T>
–jeroen
lhengen said
TNofitier should be TNotifier