I found that Delphi generics TObjectList with an attached TComparer behaves very strange. I struggle into that problem while switching my code from normal TObjectList to TObjectList.
I found that Delphi generics TObjectList with an attached TComparer behaves very strange. I struggle into that problem while switching my code from normal TObjectList to TObjectList.
The heavy to understand background is the fact that the Remove(const Value: T) suddenly uses "undocumented" the TComparer and not the pointer. That works fine as long as there are not "key doubles" in the list. I know this sounds rather technical and I have prepared a small sample to make the problem visible.
RunTest(False) works RunTest(True) fails
Perhaps I should stop using TObjectList.Create(const AComparer: IComparer; AOwnsObjects: Boolean)
---
unit uTest;
interface
uses classes;
type
TPerson = class(TObject)
public
Name: string;
RecID: integer;
constructor Create(AName: string; ARecID: integer);
end;
TMyTest = class(TObject)
procedure RunTest(AAddComparer: boolean);
end;
implementation
uses system.Generics.Collections, system.Generics.Defaults, sysUtils;
procedure TMyTest.RunTest(AAddComparer: boolean);
var
oList: TObjectList;
oPers: TPerson;
iRemovedIdx, iLast: integer;
comparison: TComparison;
begin
if AAddComparer then
begin
comparison:=
function(const left, right: TPerson): Integer
begin
Result:= CompareText(left.Name,right.Name);
end;
oList:= TObjectList.Create(TComparer.Construct(comparison),True);
end
else begin
oList:= TObjectList.Create;
end;
try
oList.Add(TPerson.Create('Tom',1));
oList.Add(TPerson.Create('Tom',2));
iLast:= oList.Count-1;
oPers:= oList[iLast];
Assert(oPers.RecID = 2); // was the last record select?
iRemovedIdx:= oList.Remove(oPers);
Assert(iRemovedIdx=iLast) // was the last record deleted?
finally
oList.Free;
end;
end;
constructor TPerson.Create(AName: string; ARecID: integer);
begin
inherited Create;
Name:= AName;
RecID:= ARecID;
end;
end.
The heavy to understand background is the fact that the Remove(const Value: T) suddenly uses "undocumented" the TComparer and not the pointer. That works fine as long as there are not "key doubles" in the list. I know this sounds rather technical and I have prepared a small sample to make the problem visible.
RunTest(False) works RunTest(True) fails
Perhaps I should stop using TObjectList
---
unit uTest;
interface
uses classes;
type
TPerson = class(TObject)
public
Name: string;
RecID: integer;
constructor Create(AName: string; ARecID: integer);
end;
TMyTest = class(TObject)
procedure RunTest(AAddComparer: boolean);
end;
implementation
uses system.Generics.Collections, system.Generics.Defaults, sysUtils;
procedure TMyTest.RunTest(AAddComparer: boolean);
var
oList: TObjectList
oPers: TPerson;
iRemovedIdx, iLast: integer;
comparison: TComparison
begin
if AAddComparer then
begin
comparison:=
function(const left, right: TPerson): Integer
begin
Result:= CompareText(left.Name,right.Name);
end;
oList:= TObjectList
end
else begin
oList:= TObjectList
end;
try
oList.Add(TPerson.Create('Tom',1));
oList.Add(TPerson.Create('Tom',2));
iLast:= oList.Count-1;
oPers:= oList[iLast];
Assert(oPers.RecID = 2); // was the last record select?
iRemovedIdx:= oList.Remove(oPers);
Assert(iRemovedIdx=iLast) // was the last record deleted?
finally
oList.Free;
end;
end;
constructor TPerson.Create(AName: string; ARecID: integer);
begin
inherited Create;
Name:= AName;
RecID:= ARecID;
end;
end.
I'm not sure I'd call this undocumented.
ReplyDeleteThe help for Remove() says "Remove first occurrence of value" while the help for the constructor says "AComparer is an equality comparator function. If not provided, the default comparator for the type is used".
For me it's pretty clear the comparer is used to find the element in Remove(), and the default comparer for an object just compares the pointers.
Asbjørn Heid Yes and no. I see your point but could argue that method using a compare should always have an overload to specify a specific one. As
ReplyDeleteprocedure Sort(const AComparer: IComparer); overload;
function BinarySearch(...IComparer): Boolean; overload;
But adding all this methods would bloat the generics again to much.
I think I have to live with this trap.
thank you for the feedback
Günther Schoch Yeah, that's fair. IndexOf() for example should absolutely have an overloaded version taking an IComparer, and so on.
ReplyDelete