Reputation: 41658
Why does Enumerable.SequenceEqual take its comparer as an IEqualityComparer? The algorithm seems to make no use of GetHashCode. Why does it not instead take a Func<TSource, TSource, bool>
predicate, similar to how First takes a Func<TSource, bool>
?
Upvotes: 5
Views: 1505
Reputation: 2430
I'm not really answering your question, but here are the overloads:
public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second,
Func<TSource, TSource, bool> predicate)
{
return first.SequenceEqual(second, new PredicateEqualityComparer<TSource>(predicate));
}
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, Func<TKey, TKey, bool> predicate)
{
return outer.Join(inner, outerKeySelector, innerKeySelector, resultSelector, new PredicateEqualityComparer<TKey>(predicate));
}
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer,
IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector,
Func<TOuter, IEnumerable<TInner>, TResult> resultSelector, Func<TKey, TKey, bool> predicate)
{
return outer.GroupJoin(inner, outerKeySelector, innerKeySelector, resultSelector,
new PredicateEqualityComparer<TKey>(predicate));
}
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TKey, TKey, bool> predicate)
{
return source.GroupBy(keySelector, new PredicateEqualityComparer<TKey>(predicate));
}
public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, TKey, bool> predicate)
{
return source.GroupBy(keySelector, elementSelector, new PredicateEqualityComparer<TKey>(predicate));
}
public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> resultSelector,
Func<TKey, TKey, bool> predicate)
{
return source.GroupBy(keySelector, resultSelector, new PredicateEqualityComparer<TKey>(predicate));
}
public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector,
Func<TKey, IEnumerable<TElement>, TResult> resultSelector, Func<TKey, TKey, bool> predicate)
{
return source.GroupBy(keySelector, elementSelector, resultSelector, new PredicateEqualityComparer<TKey>(predicate));
}
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source,
Func<TSource, TSource, bool> predicate)
{
return source.Distinct(new PredicateEqualityComparer<TSource>(predicate));
}
public static IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TSource, bool> predicate)
{
return first.Union(second, new PredicateEqualityComparer<TSource>(predicate));
}
public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first,
IEnumerable<TSource> second, Func<TSource, TSource, bool> predicate)
{
return first.Intersect(second, new PredicateEqualityComparer<TSource>(predicate));
}
public static IEnumerable<TSource> Except<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second,
Func<TSource, TSource, bool> predicate)
{
return first.Except(second, new PredicateEqualityComparer<TSource>(predicate));
}
public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TKey, TKey, bool> predicate)
{
return source.ToDictionary(keySelector, new PredicateEqualityComparer<TKey>(predicate));
}
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, TKey, bool> predicate)
{
return source.ToDictionary(keySelector, elementSelector, new PredicateEqualityComparer<TKey>(predicate));
}
public static ILookup<TKey, TSource> ToLookup<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TKey, TKey, bool> predicate)
{
return source.ToLookup(keySelector, new PredicateEqualityComparer<TKey>(predicate));
}
public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value,
Func<TSource, TSource, bool> predicate)
{
return source.Contains(value, new PredicateEqualityComparer<TSource>(predicate));
}
Achieved with this:
public class PredicateEqualityComparer<T> : IEqualityComparer<T>
{
private Func<T, T, bool> _predicate;
public PredicateEqualityComparer(Func<T, T, bool> predicate)
{
_predicate = predicate;
}
public bool Equals(T a, T b)
{
return _predicate(a, b);
}
public int GetHashCode(T a)
{
return a.GetHashCode();
}
}
I only tested SequenceEqual. Maybe some of those overloads don't work as expected because of the hashing. Its up to you, to think this through.
Upvotes: 0
Reputation: 41658
Now that .NET Core is open source, I posted this question as an issue on GitHub. Hopefully, that will translate into an answer or an improvement.
Update - details on the possible rationale for the existing design and its issues, taken from the GitHub issue:
IEqualityComparer
, despite its name, really does two things: hashes and checks for equality. The only behavioral difference between aFunc<TSource, TSource, bool>
andIEqualityComparer
is thatIEqualityComparer
has aGetHashCode
method. If all you want to do is check for sequence equality, withIEqualityComparer
you have to write the hashing code (which can be tricky to do well), even though it will likely never be used (but you can't count on it never being used becauseSequenceEqual
doesn't document that it won't use it).Often, types that you compare with
SequenceEqual
will happen to have one or moreIEqualityComparer
companion types so that they can be stored in hashed containers. Probably, that is whyIEqualityComparer
was chosen as the parameter. However, there are also plenty of times when there isn't anIEqualityComparer
and there is no hashing requirement. In those cases, having to create a class and implementGetHashCode
is wasteful.
Upvotes: 1
Reputation: 1493
I am tempted to say "because".
If you look around at other similar methods (like Enumerable.Distinct) they also take an IEqualityComparer
in the overload.
Also, an IEqualityComparer
is the "correct" way to check if objects are equal. A Func<TSource, TSource, bool>
would not neccessarily check for equality, it would check if the objects are similar enough for your specific usage at this moment in time.
Luckily it is easy enough to make your own extension method. For instance does MoreLinq have a implementation of DistinctBy
that you can look at.
Upvotes: 1