TDelegatedEqualityComparer

In this post I will try to exemplify the use of TDelegatedEqualityComparer class which can be found in Generics.Defaults unit in Delphi 2009. TDelegatedEqualityComparer is a simple class that requires two user provided routines at creation time. These routines are called each time the TDelegatedEqualityComparer must perform an equality check or hash-code generation. At first it may seem a pretty useless because you still have to declare two methods that will be passed to the TDelegatedEqualityComparer constructor thus minimizing its usefulness (you could simply create a descendant class from TEqualityComparer).  Now, in Delphi 2009 we have the “anonymous methods” that we can use “inline” when creating an instance of TDelegatedEqualityComparer class. This in what makes  TDelegatedEqualityComparer useful!

And now let’s see an example:

{ Declare a simple type. Using standard provided equality
  comparer will not work properly in this type }

type
  TMyRecord = record
  private
    FString: String;
  public
    constructor Create(const AStr: String);
  end;

{ TMyRecord }
constructor Create(const AStr: String);
begin
  FString := AStr;
end;

procedure Test();
var
  Dict: TDictionary<String, Integer>;
  Comparer: IEqualityComparer;
begin
  { Create a comparer we will use in the dictionary 
     for our custom type }
  Comparer := TDelegatedEqualityComparer.Create(
    function(const Left, Right: TMyRecord): Boolean
    begin
      { Properly compare our type! }
      Result := Left.FString = Right.FString;
    end,
    function(const Value: TMyRecord): Integer
    begin
      { Generate a hash code }
      Result := BobJenkinsHash(Value.FString[1], 
        Length(Value.FString)*2, 0);
    end);

  { Create a dictionary }
  Dict := TDictionary<String, Integer>.Create(Comparer);

  { Populate the dictionary }
  Dict.Add(TMyRecord.Create('One'), 1);
  Dict.Add(TMyRecord.Create('Two'), 2);
  Dict.Add(TMyRecord.Create('Three'), 3);

  { Check is we can find the values in the dictionary
    Our anonymous methods will be used for equality comparison
    Using the default comparer provided by CG will not work }

  ASSERT(Dict.ContainsKey(TMyRecord.Create('One')));
  ASSERT(Dict.ContainsKey(TMyRecord.Create('Two')));
  ASSERT(Dict.ContainsKey(TMyRecord.Create('Three')));

  Dict.Free();
end;

So why the default provided comparer will not work with our custom data type? Well that’s because the Generics.Defaults will create a “binary equality comparer”. This compares the memory occupied by the value, which is not good in our case because we have references to strings there. So two strings with the same value will have different addresses and thus the comparer would find those two different.

If you plan to use a certain data type multiple times in your application it is still better to create a TCustomComparer class and use it in your code everywhere you need.