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.