Search code examples
delphiequalstobjectlist

How to use methods like Contains, Remove or IndexOf of TObjectList<T>


I am having problems using methods like Contains, Remove or IndexOf of TObjectList<T> class, when T is a custom type like TSocket in the following example code.

I started by implementing a custom TSocket type and tried to use it in a list of type TObjectList<TSocket> like this:

list := nil;
socket := nil;
try
  list := TObjectList<TSocket>.Create();
  socket := TSocket.Create(TIpAddress.Parse('127.0.0.1'),6857);

  // add new socket object with equal values to list
  list.Add(TSocket.Create(TIpAddress.Parse('127.0.0.1'),6857));

  // should return true but returns false
  if list.Contains(socket) then
    WriteLn('socket contained in list')
  else
    WriteLn('socket not contained in list');

  // should return number 0 but returns -1
  if list.IndexOf(socket) = 0 then
    WriteLn('socket contained in list')
  else
    WriteLn('socket not contained in list');

  // should remove item from list but items doesn't get removed
  list.Remove(socket);

finally
  list.Free();
  socket.Free();

I expected that Contains, IndexOf and Remove make use of the Equals procedure of TMyObject and overwriten implementation of this procedure. Therefore I added the following implemenation of Equals to my TSocket class:

type
  TSocket = class
  strict private
    _ipAddress: TIpAddress;
    _port: integer;
  public
    constructor Create(ipAddress: TIpAddress; port: integer);
    function GetIpAddress: TIpAddress;
    function GetPort: integer;
    property IpAddress: TIpAddress read GetIpAddress;
    property Port: integer read GetPort;
    function Equals(other: TObject): boolean; overload; override;
    destructor Destroy; override;
  end;

implementation

constructor TSocket.Create(ipAddress: TIpAddress; port: integer);
begin
  inherited Create();
  _ipAddress := ipAddress;
  _port := port;
end;

function TSocket.Equals(other: TObject): boolean;
var
  otherSocket: TSocket;
begin
  if not (other is TSocket) then exit(false);
  otherSocket := other as TSocket;
  result:= (_ipAddress.Equals(otherSocket.IpAddress)) and (_port = otherSocket.Port)
end;

function TSocket.GetIpAddress: TIpAddress;
begin
  result := _ipAddress;
end;

function TSocket.GetPort: integer;
begin
  result := _port;
end;

destructor TSocket.Destroy;
begin
  _ipAddress.Free();
  inherited Destroy();
end;

Using this code Contains returns false but should be true, IndexOf returns -1 but should be 0 and Remove does not remove the object but should remove it. I expected these methods would use the Equals method of TSocket which they didn't. After reading the documentation I found out that the constructor of TObjectList can be called with an implemenantion of IComparer. Therefore I implementated an TEqualityComparer<TSocket> in order to use my Equals method. Unfortunately the constructor of TObjectList does not support the IEqualityComparer interface but instead use the IComparer interface.

Question: How do I use methods like Contains, Remove or IndexOf of TObjectList<T> when using custom types in Delphi? In other programming languages (like Java or C#) Equals is used to compare objects in list types. What mechanism does Delphi use to compare objects?

Update Thank you for the comprehensive feedback. I have updated my question and the code appropriately. I elaborated on what my expectations were when running the code and added further code to make my intentions clearer. @DavidHeffernan: The implementation was indeed wrong. I added inheritance to TInterfacedObject in order to learn more about reference counting. I removed TInterfacedObject from the code.


Solution

  • Your error was your assumption that TObjectList<T> uses the Equals function to test for equality.

    By default, TObjectList<T>, or more accurately TList<T>, uses the comparer returned by TComparer<T>.Default. In the case of TObjectList<TSocket>, the default comparer compare the pointer itself. Since you created 2 differents objects, the pointers differ. The result you get is the expected result.

    If you want to override that default behavior, you need to supply your own comparer. The way to do so is to pass it through the constructor like this :

    TObjectList<TSocket>.Create(TComparer<TSocket>.Construct(
        function (const L, R : TSocket) : Integer
        begin
          //Compare here.
        end)
       );
    

    Your function should:

    • Return a value lower than 0 if L is lower than R.(Typically -1)
    • Return a value greater than 0 if L is greater than R.(Typically 1)
    • Return 0 of both are equals.

    If, for whatever reasons, you want to only check for equality, you can technically do so and return either -1 or 1 when it's not equal without further comparison. That is, as long as you don't plan to sort the list or BinarySearch through it.