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.