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.

4 Comments

  1. The best way for native Delphi is to resign generics.
    And to use the C++ templates with constraints. IMHO.

    1. Comparer via delegated operation is very time consuming operation for big collections(arrays).
    2. NET constraints have to be escalated for native delphi generics since native delphi heap objects not single rooted.

    That is

    Where Arrray, String, Pointer constraints Are?

  2. >>Comparer via delegated operation is very time consuming operation for big collections(arrays).

    True, that’s why a descendant of TCustomComparer is preferred. But for small operations it’s quite handy.

    >> Arrray, String, Pointer constraints

    These aren’t really required in the real world. Because those are not objects thus cannot have inheritance. If you need a generic class that only accepts pointer types – do not make it generic in the first place.

    As for generics for native Delphi – it’s step forward for a lot of people that still prefer Object Pascal over C++.

    I, for instance prefer Delphi generics over C++ templates. (matter of taste).

  3. >>These aren’t really required in the real world. Because those are not objects >>thus cannot have inheritance. If you need a generic class that only accepts >>pointer types – do not make it generic in the first place.

    And what about Value type constraint in .NET. No inheritance.
    But it is. It makes sence for some guys.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.