Delegate Comparers - StephenCleary/Comparers GitHub Wiki
Delegate comparers are also called "anonymous comparers". This kind of "anonymous comparer" is completely different than comparers for anonymous types.
- It can be difficult to maintain the comparer invariants with two unrestricted delegates. The invariants are:
Compare(x, x) == 0
-
Compare(x, y) == 0
impliesCompare(y, x) == 0
-
(Compare(x, y) == 0) && (Compare(y, z) == 0)
impliesCompare(x, z) == 0
-
Compare(x, y) < 0
impliesCompare(y, x) > 0
-
Compare(x, y) > 0
impliesCompare(y, x) < 0
-
(Compare(x, y) < 0) && (Compare(y, z) < 0)
impliesCompare(x, z) < 0
-
(Compare(x, y) > 0) && (Compare(y, z) > 0)
impliesCompare(x, z) > 0
-
Compare(x, y) == 0
impliesGetHashCode(x) == GetHashCode(y)
- All correctly-implemented anonymous comparers are some combination of key comparers (
OrderBy/ThenBy
), compound comparers (ThenBy
), reverse comparers (Reverse
), and sequence comparers (Sequence
); and it's cleaner to use the fluent API to spell out the comparison algorithm explicitly. - The fluent API will create the correct
GetHashCode
implementation automatically.
For these reasons, I strongly recommend against using the AnonymousComparer
and AnonymousEqualityComparer
classes. Anonymous comparers cannot be created using the normal Compare
class; you have to create them explicitly.
Anonymous comparers can be created from a delegate:
// Treat all 0s and 1s as equivalent, all 2s and 3s as equivalent, etc.
IComparer<int> comparer = new AnonymousComparer<int>
{
Compare = (x, y) => Compare<int>.Default().Compare(x / 2, y / 2),
};
However, it's highly recommended that you provide a corresponding delegate for GetHashCode
as well:
// Treat all 0s and 1s as equivalent, all 2s and 3s as equivalent, etc.
IComparer<int> comparer = new AnonymousComparer<int>
{
Compare = (x, y) => Compare<int>.Default().Compare(x / 2, y / 2),
GetHashCode = x => Compare<int>.Default().GetHashCode(x / 2),
};
That allows hash-based collections and algorithms to work as expected.
Note that any AnonymousComparer
can be written more succinctly with the fluent API. The code above can also be written as:
// Treat all 0s and 1s as equivalent, all 2s and 3s as equivalent, etc.
IComparer<int> comparer = Compare<int>.OrderBy(x => x / 2);
Not only is the code shorter and more clearly defines the comparer algorithm, it is also easier to maintain since it automatically creates a correct GetHashCode
implementation.
For equality comparers, you can create a AnonymousEqualityComparer
instance and set the Equals
and GetHashCode
delegates. Note that GetHashCode
is not optional for equality comparers.