2

To explain my question I'll use something very similar to the TDictionary example in Embarcadero's website, but my class implements IComparable.

In Delphi (XE7):

TCity = class(TInterfacedObject, IComparable)
  Country: String;
  Latitude: Double;
  Longitude: Double;

  function CompareTo(Obj: TObject): Integer;
end;

I want a TDictionary with cities as keys:

Dictionary := TDictionary<TCity, string>.Create;

CityA := TCity.Create;
CityA.Country := 'United Kingdom';
CityA.Latitude := 51.5;
CityA.Longitude := -0.17;
Dictionary.Add(CityA, 'London');

Now, if I create a new city with the same values as before where CityA.CompareTo(CityB) returns 0, like:

CityB := TCity.Create;
CityB.Country := 'United Kingdom';
CityB.Latitude := 51.5;
CityB.Longitude := -0.17;

And before adding CityB to the dictionary, I was expecting that:

Dictionary.ContainsKey(CityB)

would use my CompareTo implementation and ContainsKey would return True, but that is not the case. It seems that my class will also need to inherit from TEqualityComparer.

So how do I add an equality comparer—used by ContainsKey—to my class, additional to the CompareTo already there?

I know I could use a TDelegatedEqualityComparer and anonymous methods to create a custom comparer to be used when creating the dictionary. Something like:

Dictionary := TDictionary<TCity, string>.Create(CityComparer)

But is there a way to include the equality comparer in TCity, so that I don't need to have one when creating the dictionary?

2 Answers 2

7

You are mixing two things here. IComparable is meant for sorting.

IEqualityComparer<T> is to check if two items are equal or not but it also has the GetHashCode method that the dictionary is using internally.

For objects the default implementation for the IEqualityComparer<T> uses the virtual GetHashCode and Equals methods from TObject. So if you want to treat two different instances as equal you have two options:

  1. override these two methods in your class

  2. provide a custom implementation of IEqualityComparer<T>

1
  • You are right. I realised, after trying it, that IComparable wouldn't be used in ContainsKey. But I need IComparable, so that's why I was asking on how to implement IComparable, and inherit from TEqualityComparer.What didn't occur to me is that I can override Equals and GetHashCode without inheriting from TEqualityComparer—those methods are already part of TObject. So your option 1 did exactly what I wanted.
    – alondono
    Commented Aug 20, 2015 at 0:50
1

Not as far as I know. If you don't provide an IEqualityComparer in then constructor TDictionary wil use the default one for the key.

Look in the constructor :

constructor TDictionary<TKey,TValue>.Create(ACapacity: Integer; const AComparer: IEqualityComparer<TKey>);
var
  cap: Integer;
begin
  inherited Create;
  if ACapacity < 0 then
    raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange);
  FComparer := AComparer;
  if FComparer = nil then
    FComparer := TEqualityComparer<TKey>.Default; // <-- HERE!
  SetCapacity(ACapacity);
end;

Her here in GetBucketIndex it is used

    function TDictionary<TKey,TValue>.GetBucketIndex(const Key: TKey; HashCode: Integer): Integer;
begin    
 ...
        // Found: return location.
        if (hc = HashCode) and FComparer.Equals(FItems[Result].Key, Key) then
...
end;

So no, you'll have to give the IComparable in the constructor

4
  • 3
    If you look at the actual implementation of TEqualityComparer<T>.Default, it checks if T (which would be TCity in this case) implements IEqualityComparer<T> and if so then returns a pointer to that implementation, thus TDictionary<TCity> can use TCity's implementation of IEqualityComparer<TCity>. Commented Aug 19, 2015 at 6:18
  • @RemyLebeau I did not see anything (XE8), where there is a lookup to an implemented interface. But I see some routing to Equals_Class and GetHashCode_Class (with a nice default hash value 42). The default equality comparer will use the methods Equals and GetHashCode from the class
    – Sir Rufo
    Commented Aug 19, 2015 at 7:40
  • 2
    IOW, no need to implement and pass in IEqualityComparer<TCity>, just override TCity.Equals. Commented Aug 19, 2015 at 8:56
  • Thank you @RudyVelthuis, it is exactly what I ended up doing. It didn't occur to me that I could simply override Equals and GetHashCode without the need to implement IEqualityComparer.
    – alondono
    Commented Aug 20, 2015 at 5:14

Not the answer you're looking for? Browse other questions tagged or ask your own question.