sortingdelphicollectionsfiremonkeytlist

Cannot get TList<>.Sort() to work in Delphi 11 FMX


I have been trying to sort a collection of objects. While I have seen many promising solutions, my compiler simply won’t accept them (error message in procedure GenReorderSpeciesList). I am using Delphi 11 FMX on Windows 10. I hope someone can direct me to some assistance.

uses
  System.SysUtils,
  System.Generics.Collections, System.Contnrs, System.Math,
  uIS_LSAReportsTypes;

function CompareMyObjects(Item1, Item2: TSpecies): Integer;
procedure GenReorderSpeciesList(var aSpcList : TList<TSpecies>);

implementation

{
 TSpecies = class
    SubFamID : integer;
    SubSpcID : integer;
    SPGroup : string;
    Family : string;
    Subfamily : string;
}

function CompareMyObjects(Item1, Item2: TSpecies): Integer;
begin
  // Sort by Field1 (ascending, case-insensitive)
  Result := AnsiCompareText(Item1.SPGroup, Item2.SPGroup);   // Returns a value less than 0 if S1 < S2,
                                                             // a value greater than 0 if S1 > S2,
                                                             // and 0 if S1 = S2
  if Result <> 0 then Exit;

  // If Field1 values are equal, sort by Field2 (ascending, case-insensitive)
  Result := AnsiCompareText(Item1.Family, Item2.Family);
  if Result <> 0 then Exit;

  // If Field1 and Field2 are equal, sort by Field3 (descending)
  if Item1.Subfamily < Item2.Subfamily then
    Result := 1 // Item1 comes after Item2
  else if Item1.Subfamily > Item2.Subfamily then
    Result := -1 // Item1 comes before Item2
  else
    Result := 0; // All fields are equal
end;

procedure GenReorderSpeciesList(var aSpcList : TList<TSpecies>);
begin
  aSpcList<TSpecies>.Sort(@CompareMyObjects); // [dcc64 Error] uIS_LSAReportsGenfunctions.pas(47): E2014 Statement expected, but expression of type 'Boolean' found

// ALSO produces same error
// aSpcList<TSpecies>.Sort(CompareMyObjects);     
end;

The error is:

[dcc64 Error] uIS_LSAReportsGenfunctions.pas(47): E2014 Statement expected, but expression of type 'Boolean' found


Solution

  • First, there is a typo in your code: aSpcList<TSpecies> needs to be just aSpcList. Don't specify Generic parameters on variables, only on types.

    Second, whereas the non-Generic TList.Sort() method expects a raw function pointer like you are doing, the Generic TList<T>.Sort() overload that takes a custom comparator expects an IComparer<T> interface instead.

    So, you can re-write CompareMyObjects to be a class that implements the ICompare<T>.Compare() method, and then you can pass an instance of that class to TList<T>.Sort(), eg:

    type
      TSpeciesListComparer = class(TInterfacedObject, IComparer<TSpecies>)
        function Compare(const Left, Right: TSpecies): Integer;
      end;
    
    ...
    
    function TSpeciesListComparer.Compare(const Left, Right: TSpecies): Integer;
    begin
      Result := ...;
    end;
    
    procedure GenReorderSpeciesList(aSpcList : TList<TSpecies>);
    var
      Comparer: IComparer<TSpecies>;
    begin
      Comparer := TSpeciesListComparer.Create;
      aSpcList.Sort(Comparer);
    end;
    

    Alternatively, the RTL provides some default implementations of IComparer<T> that you can use, such as TDelegatedComparer, which would allow you to use your existing CompareMyObjects() function (if you can tweak its signature to add const to its parameters), eg:

    function CompareMyObjects(const Item1, Item2: TSpecies): Integer;
    begin
      Result := ...;
    end;
    
    procedure GenReorderSpeciesList(aSpcList : TList<TSpecies>);
    var
      Comparer: IComparer<TSpecies>;
    begin
      Comparer := TDelegatedComparer<TSpecies>.Create(CompareMyObjects);
      aSpcList.Sort(Comparer);
    end;
    

    If, for whatever reason, you can't tweak the signature, you can wrap it with an anonymous function instead, eg:

    function CompareMyObjects(Item1, Item2: TSpecies): Integer;
    begin
      Result := ...;
    end;
    
    procedure GenReorderSpeciesList(aSpcList : TList<TSpecies>);
    var
      Comparer: IComparer<TSpecies>;
    begin
      Comparer := TDelegatedComparer<TSpecies>.Create(
        function(const Left, Right: TSpecies): Integer
        begin
          Result := CompareMyObjects(Left, Right);
        end
      );
      aSpcList.Sort(Comparer);
    end;