Edward Brey
Edward Brey

Reputation: 41658

Why does SequenceEqual take an IEqualityComparer rather than a predicate?

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

Answers (3)

Awesomni.Codes
Awesomni.Codes

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

Edward Brey
Edward Brey

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 a Func<TSource, TSource, bool> and IEqualityComparer is that IEqualityComparer has a GetHashCode method. If all you want to do is check for sequence equality, with IEqualityComparer 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 because SequenceEqual 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 why IEqualityComparer was chosen as the parameter. However, there are also plenty of times when there isn't an IEqualityComparer and there is no hashing requirement. In those cases, having to create a class and implement GetHashCode is wasteful.

Upvotes: 1

aanund
aanund

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

Related Questions