IComaprer & IComparable Refactoring Proposal

We are exploring the possiblility of changing the design of IComparer<T> and IComparable<T> interfaces that will ship with Whidbey. This post describes some more detail on the issues we are trying to address with the design change and we are looking for feedback on the proposal.

Background

The first version of the .NET Framework had three main comparison related-interfaces: IComparable, IComparer, and IHashCodeProvider. One of the main issues with the interfaces was that there was no single interface that could be used by Hashtable to override the default identity of the keys (change which keys compare to equal and which not). You needed to use IHashCodeProvider to provide custom hash codes for the keys and IComparer to provide custom equality (via Compare==0). This resulted in a common usage mistake (a bug) where a user would pass incompatible pair of IHashCodeProvider and IComparer to the Hashtable constructor. For example, a case sensitive hash code provider and a case insensitive comparer. This would result in a bug where two strings that do compare equal could have different hash codes, and so the Hashtable indexer could not find the item with the given key.

We tried to fix this problem in Whidbey generic APIa and decided to put all comparison related methods from IComarer and IHashCodeProvider into a single interface (IComparer<T>), so users could not pass two incompatible implementations of the interfaces to the Hashtable constructor. We have always seen the new design as a compromise between a very clean OO design and trying to avoid explosion of the number of interfaces.

Well, we have received feedback from people using the new interfaces (IComparer<T> and IComparable<T>) that made us thinking that may be we compromised a bit too much J Here are the relevant links describing the issues:

We decided to give it one more shot to fix the concerns. You can find the proposal for the new design below. I think the change proposal addresses most of the concerns, but at the same time it has the following drawbacks:

  • More interfaces to understand. Makes the Framework less approachable.
  • Custom comparers can implement one interface (let’s say IEqualityProvider<T>) but not the other (IComparer<T>). Switching from one collection which uses IEqualityProvider<T> to another which happens to use IComparer<T> may be difficult (as you will need a different custom comparer).

 

I am looking for feedback on the proposal from people who have experimented with the interfaces. The change is something that we would do only if there is an overwhelming positive feedback from the community, not if the improvement seems only marginal.

Thanks!

 

Current Design

public interface IComparable<T> {

    bool Equals(T other);

    int CompareTo(T other);

}

 

public interface IComparer<T> {

    bool Equals(T x, T y);

int Compare(T x, T y);

int GetHashCode(T obj);

}

 

public interface IKeyComparer : IComparer, IHashCodeProvider {

    bool Equals(object x, object y);

}

Proposed New Design

IComparable<T>

· Break into two interfaces

public interface IEquateable<T> {

bool Equals(T other);

}

 

public interface IComparable<T> {

    int CompareTo(T other);

}

IComparer<T>

· Break into two interfaces

public interface IEqualityProvider<T> {

    bool Equals(T x, T y);

int GetHashCode(T obj);

}

 

public interface IComparer<T> {

int Compare(T x, T y);

}

IKeyComparer

· Rename from IKeyComparer to IHashComparer

· Remove IComparer requirement (base type)

public interface IEqualityProvider : IHashCodeProvider {

    bool Equals(object x, object y);

}