简体   繁体   中英

Creating a custom equality comparer for IEnumerables<T> when T is IEnumerable

I want to have a custom equality comparer IEnumerable s. using @PaulZahra 's code , I created the following class:

class CustomEqualityComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
    {
        var enumerables = new Dictionary<T, uint>();

        foreach (T item in x)
        {
            enumerables.Add(item, 1);
        }

        foreach (T item in y)
        {
            if (enumerables.ContainsKey(item))
            {
                enumerables[item]--;
            }
            else
            {
                return false;
            }
        }

        return enumerables.Values.All(v => v == 0);
    }

    public int GetHashCode(IEnumerable<T> obj) => obj.GetHashCode();
}

The problem is that if T itself is an IEnumerable , then ContainsKey will check for reference equality, while the point of this equality comparer is to check for value equality at any given depth .

I thought to use .Keys.Contains() instead, since it can accept an IEqualityComparer as an argument:

if (enumerables.Keys.Contains(item, this)) // not sure if "this" or a new object

but I get the following error ( CS1929 ):

'Dictionary.KeyCollection' does not contain a definition for 'Contains' and the best extension method overload 'Queryable.Contains(IQueryable, T, IEqualityComparer)' requires a receiver of type 'IQueryable'

I am not sure how to approach this. How to fix it? Thanks.


Edit: Note that this comparer doesn't care about order.

To have such recursive comparer you simply need pass proper comparer to Dictionary if T is enumerable. I think getting type T from IEnumerable<T> and then equivalent of new Dictionary<U, uint>(new CustomEqualityComparer<U>) (using Create instance of generic type? ) should achieve what you want.

Notes:

  • you must provide correct implementation of GetHashCode that matches Equals if you use comparer for any dictionary/ HashSet . Default Equals for sequences is reference compare that does not align with your Equals . Note that most implementation of GetHashCode depend on order of the items in the collection - so you need to find one that works for sets. Ie sum of hash codes of each item would do, probably making distribution slightly worse.
  • you may want to LINQ set operations instead of doing them by hand. All operations like Distinct already take comparers. In case of "sets are the same" you can use Distinct - x.Distinct(y, comparerBuiltViaReflection)
  • Beware of limitations of such code: not every enumerable can be enumerated more than once (user input, network streams,..) or may produce different result on re-iteration ( while(count < 10){ count ++; yield return random.Next(); } ), cost of re-iteartion many be significant (re-read all lines in huge file on each iteration) or enumerable can represent infinite sequence ( while(true){ yield return count++; } ).
  • As others have mentioned, IEnumerable<T> can enumerate forever, so it's dangerous to do this on that interface. I'd recommend using ICollection<T> instead- it has a fixed size. And you'll find it will work for most any type you'd want to use anyway.

  • Furthermore, I'd recommend using TryGetValue to reduce the number of times you need to look up into the dictionary.

  • Your code is not correctly keeping the count of each item in the first enumerable.

  • GetHashCode needs to take into account every member of the enumerable.

All that being said, here is an adjustment of your implementation:

class CustomEqualityComparer<T> : IEqualityComparer<ICollection<T>>
{
    public bool Equals(ICollection<T> x, ICollection<T> y)
    {
        if (x.Count != y.Count) return false;
        var enumerables = new Dictionary<T, uint>(x.Count);

        foreach (T item in x)
        {
            enumerables.TryGetValue(item, out var value);
            enumerables[item] = value + 1;
        }

        foreach (T item in y)
        {
            var success = enumerables.TryGetValue(item, out var value);
            if (success)
            {
                enumerables[item] = value - 1;
            }
            else
            {
                return false;
            }
        }

        return enumerables.Values.All(v => v == 0);
    }

    public int GetHashCode(ICollection<T> obj)
    {
         unchecked
         {
             var hashCode = 0;

             foreach(var item in obj)
             {
                 hashCode += (item != null ? item.GetHashCode() : 0);                
             }
             return hashCode;
         }

     }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM