Ad

TObjectList.IndexOf Giving Incorrect Result

- 1 answer

I am trying to understand why the way I use TObjectList<T>.IndexOf is not working for me.

The following is a small example

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  madExcept,
  madLinkDisAsm,
  madListHardware,
  madListProcesses,
  madListModules,
  System.Generics.Defaults,
  System.Generics.Collections,
  System.Contnrs,
  System.SysUtils;

type
  TRecordObject = class(TObject)
    ID: Integer;
    Price: Currency;
    Matched: Boolean;
  public
    constructor Create(aSort: Integer; aPrice, aSize: Currency; aID: string; aNewParam: Integer;
      aSecondPrice, aSecondSize: Currency; aMatched: boolean); reintroduce;
  end;

  TSortCriterion<T> = class(TObject)
    Ascending: Boolean;
    Comparer: IComparer<T>;
  end;

  TSortCriteriaComparer<T> = class(TComparer<T>)
  private
    SortCriteria: TObjectList<TSortCriterion<T>>;
  public
    constructor Create;
    destructor Destroy; override;
    function Compare(const Right, Left: T): Integer; override;
    procedure ClearCriteria; virtual;
    procedure AddCriterion(NewCriterion: TSortCriterion<T>); virtual;
  end;

  TIDComparer = class(TComparer<TRecordObject>)
  public
    function Compare(const Left, Right: TRecordObject): Integer; override;
  end;

  TMatchedComparer = class(TComparer<TRecordObject>)
  public
    function Compare(const Left, Right: TRecordObject): Integer; override;
  end;

procedure TSortCriteriaComparer<T>.AddCriterion(NewCriterion: TSortCriterion<T>);
begin
  SortCriteria.Add(NewCriterion);
end;

procedure TSortCriteriaComparer<T>.ClearCriteria;
begin
  SortCriteria.Clear;
end;

function TSortCriteriaComparer<T>.Compare(const Right, Left: T): Integer;
var
  Criterion: TSortCriterion<T>;
begin
  for Criterion in SortCriteria do
  begin
    Result := Criterion.Comparer.Compare(Right, Left);
    if not Criterion.Ascending then
      Result := -Result;
    if Result <> 0 then
      Exit;
  end;
end;

constructor TSortCriteriaComparer<T>.Create;
begin
  inherited;
  SortCriteria := TObjectList<TSortCriterion<T>>.Create(True);
end;

destructor TSortCriteriaComparer<T>.Destroy;
begin
  SortCriteria.Free;
  inherited;
end;



    
function TIDComparer.Compare(const Left, Right: TRecordObject): Integer;
begin
  if Left.ID > Right.ID then
    Result := 1
  else if Left.ID < Right.ID then
    result := -1
  else
    result := 0;
end;

constructor TRecordObject.Create(aSort: Integer; aPrice, aSize: Currency; aID: string;
  aNewParam: Integer; aSecondPrice, aSecondSize: currency; aMatched: boolean);
begin
  ID := aSort;
  Price := aPrice;
  Matched := aMatched;
end;

var
  MyComparer: TSortCriteriaComparer<TRecordObject>;
  Criterion: TSortCriterion<TRecordObject>;
  MyList: TObjectList<TRecordObject>;
  MyObject: TRecordObject;
  ReturnValue: Integer;
  Result: Boolean;

function TMatchedComparer.Compare(const Left, Right: TRecordObject): Integer;
begin
  if Left.Matched > Right.Matched then
    Result := 1
  else if Left.Matched < Right.Matched then
    result := -1
  else
    result := 0;
end;

var
  SearchObject: TRecordObject;

begin
  MyComparer := TSortCriteriaComparer<TRecordObject>.Create;
  try
    Criterion := TSortCriterion<TRecordObject>.Create;
    Criterion.Ascending := True;
    Criterion.Comparer := TIDComparer.Create;
    MyComparer.AddCriterion(Criterion);

    Criterion := TSortCriterion<TRecordObject>.Create;
    Criterion.Ascending := True;
    Criterion.Comparer := TMatchedComparer.Create;
    MyComparer.AddCriterion(Criterion);

    MyList := TObjectList<TRecordObject>.Create;

    MyObject := TRecordObject.Create(26, 1, 1, '', 1, 1, 1, False);
    MyList.Add(MyObject);

    MyObject := TRecordObject.Create(26, 1, 1, '', 1, 1, 1, True);
    MyList.Add(MyObject);

    MyObject := TRecordObject.Create(24, 1, 1, '', 1, 1, 1, True);
    MyList.Add(MyObject);

    MyObject := TRecordObject.Create(24, 1, 1, '', 1, 1, 1, True);
    MyList.Add(MyObject);

    MyObject := TRecordObject.Create(34, 1, 1, '', 1, 1, 1, False);
    MyList.Add(MyObject);

    MyList.Sort(MyComparer);

    SearchObject := TRecordObject.Create(26, 1, 1, '', 1, 1, 1, True);

    // Result=3 (correct)
    Result := MyList.BinarySearch(SearchObject, ReturnValue, MyComparer);

    Writeln(Result);
    Writeln('ReturnValue with BinarySearch=' + IntToStr(ReturnValue));

    //Result=-1=not found (incorrect)
    ReturnValue := MyList.IndexOf(SearchObject);
    Writeln('ReturnValue with IndexOf=' + IntToStr(ReturnValue));
    Readln;

  finally
    Criterion.Free;
    MyComparer.Free;
    MyList.Free;
  end;
end.

If I use TObjectList<T>.BinarySearch I have the correct result of '3' but if I use TObjectList<T>.IndexOf then I have -1 (not found).

SearchObject is used here only to make sure the two objects passed to .BinarySearch and IndexOf are the same.

I tried to execute .IndexOf before .BinarySearch because I thought that after a search something had to be resetted but that did not work either.

What am I doing wrong?

EDIT

I have also replaced TObjectList<T> with TList<T> but the same error is still there.

Ad

Answer

The binary search call is passed your custom comparer, and so knows how to identify the objects according to the rule that you codify there. But your call to IndexOf is not passed your comparer, and since you didn't provide one when you created the collection, the default comparer is used.

The default comparer uses object identity, and since your search object is not in the collection, -1 is returned, which is the correct answer to the question you asked.

Solution: pass the comparer to the collection constructor.

MyList := TObjectList<TRecordObject>.Create(MyComparer);
Ad
source: stackoverflow.com
Ad