2

How can I free a generic TList<T>?

I know I can use a TObjectList with AOwnsObjects = True while creating it.

I am curious, how can I rewrite the following method in a generic fashion so it can free T when T is an unmanaged reference, either a pointer or a class?

procedure FreeList(const List: TList);
var
  i: Integer;
begin
  if (List = nil) then
    Exit;
  for i := Pred(List.Count) downto 0 do
    if Assigned(List[i]) then
      TObject(List[i]).Free;
  List.Clear;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Fabio Vitale
  • 2,257
  • 5
  • 28
  • 38
  • This doesn't free the list, just clears it. – Dsm Jul 17 '19 at 12:57
  • 1
    Why do you need to handle arbitrary element types? Use `TObjectList` and there is nothing more to do. – David Heffernan Jul 17 '19 at 13:18
  • Related: see [Testing the type of a generic in delphi](https://stackoverflow.com/questions/31042997/) and [Undocumented intrinsic routines](https://stackoverflow.com/questions/30417218/) – Remy Lebeau Jul 17 '19 at 20:05

2 Answers2

4

You can add a T Generic parameter to your procedure (which must then be made into a class method in order to use Generics with it), and use RTTI to check if T is a class type before calling Free() on your list elements.

For example:

type
  ListUtils = class
  public
    class procedure ClearList<T>(const List: TList<T>);
  end;

class procedure ListUtils.ClearList<T>(const List: TList<T>);
type
  PObject = ^TObject;
var
  i: Integer;
  Value: T;
begin
  if (List = nil) then
    Exit;
  if GetTypeKind(T) = tkClass then
  // for older compilers that do not have GetTypeKind():
  // if PTypeInfo(TypeInfo(T))^.Kind = tkClass then
  begin
    for i := Pred(List.Count) downto 0 do
    begin
      Value := List[i];
      PObject(@Value)^.Free;
    end;
  end;
  List.Clear;
end;

Alternatively:

uses
  ..., System.Rtti;

type
  ListUtils = class
  public
    class procedure ClearList<T>(const List: TList<T>);
  end;

class procedure ListUtils.ClearList<T>(const List: TList<T>);
var
  i: Integer;
begin
  if (List = nil) then
    Exit;
  if GetTypeKind(T) = tkClass then
  // for older compilers that do not have GetTypeKind():
  // if PTypeInfo(TypeInfo(T))^.Kind = tkClass then
  begin
    for i := Pred(List.Count) downto 0 do
      TValue.From<T>(List[i]).AsObject.Free;
  end;
  List.Clear;
end;

Then you can use it like this:

var
  IntList: TList<Integer>;
  ObjList: TList<TSomeClass>;
begin
  IntList := TList<Integer>.Create;
  ...
  // does not call TObject.Free on list elements
  ListUtils.ClearList<Integer>(IntList);
  IntList.Free;

  ObjList := TList<TSomeClass>.Create;
  ...
  // calls TObject.Free on list elements
  ListUtils.ClearList<TSomeClass>(ObjList);
  ObjList.Free;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you @Remy: as stated, my question was generated by simple curiosity and your answer satisfied it. – Fabio Vitale Jul 18 '19 at 12:49
  • Does not compile under Berlin: two errors. The fisrst is E2530 Type parameters not allowed on global procedure or funcion FreeList, the second is E2003 Udeclared identifier 'T' at the method signature const List: TList – Fabio Vitale Jul 18 '19 at 12:57
  • @FabioVitale oh yeah, I keep forgetting about the issue that Generics don't work on standalone procedures (I really hate that restriction). So, just make the procedure be a `class` method instead. I updated my answer to show that – Remy Lebeau Jul 18 '19 at 16:34
  • Hi @Remy, still I get an error E2089 Invalid Typecast at the line TObject(List[i]).Free; – Fabio Vitale Jul 19 '19 at 07:48
  • Thank you so much @Remy: it works like a charm and now I have a better grasp on the whole thing! – Fabio Vitale Jul 19 '19 at 09:25
0

Use the generic version, TObjectList<T>.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
HeartWare
  • 7,464
  • 2
  • 26
  • 30