GroupBy HashSet 不分组,而 SetEquals 为真

GroupBy HashSet not grouping, whilst SetEquals is true

我有这样一种情况,我需要一个集合在 HashSet<myClass>GroupBy,其中 myClass 覆盖 Equals(myClass)Equals(object)GetHashCode()==!=.

当我执行 GroupBy() 时,结果没有分组。 Distinct() 也是如此。它是在对 myClass 的值调用 ToHashSet() 的大型 LINQ 查询中创建的。然后使用结果,其中生成的 HashSet 本身是 Dictionary<HashSet<myClass>, someOtherCollection>.

的键

我已将问题提炼为最简单的情况,其中两个 HashSet<myClass>myHashSet1myHashSet2 都只包含相同的单个元素。如果我调用 myHashSet1.Equals(myHashSet2) 它 returns false,而 myHashSet1.SetEquals(myHashSet2) returns true.

这里做错了什么?当所有元素都匹配时,如何创建 GroupBy 组 HashSet?

可能的一步是 其中解释了如何覆盖 HashSet 的默认 IEqualityComparer。但如果这是答案的一部分,剩下的关键问题就是如何让 GroupBy 知道使用这个相等比较器?

我假设我应该在调用 ToHashSet() 时喂它,也许 ToHashSet(myHashSetEqualityComparer<myClass>),但它只需要 ToHashSet(IEqualityComparer<myClass>),而不是 ToHashSet(IEqualityComparer<HashSet<myClass>>)

这里是 myClass 的代码,提炼为要点:

public class myClass : myBaseClass, IEquatable<myClass>
{   
    public string Prop1 { get; set; }
    public string Prop2 { get; set; }
    public Guid Prop3 { get; set; }

    public override bool Equals(myClass other)
    {    
      if (Equals(other, null)) return false;

      return (Prop1 == other.Prop1 && Prop2 == other.Prop2 && Prop3 == other.Prop3);
    }

    public override bool Equals(object obj)     
    {
      if (Equals(obj, null) || !(obj is myClass))
        return false;

     return Equals((myClass)obj);
    }

    public static bool operator ==(myClass left, myClass right)
    {
     if (Object.Equals(left, null))
        return (Object.Equals(right, null)) ? true : false;
     else
        return left.Equals(right);
    }

    public static bool operator !=(myClass left, myClass right)
    {
     return !(left == right);
    }

    public override int GetHashCode()
    {
     return Prop3.GetHashCode() + 31 * (Prop2.GetHashCode() + 31 * Prop1.GetHashCode());
    }
 }

注意:我之前问过这个问题,但是参考上面的链接问题关闭了,没有回答原始问题,明确指出这是关于 GroupBy 问题。我在此处添加了有关在 LINQ 中使用的更多详细信息以进行说明。

编辑 根据评论中的请求这就是我正在做的事情:

var myGroupedResult = myUngroupedCollection.
       GroupBy(x => x.Value).
       ToDictionary(x => x.Key, x => x.ToList());
// myUngroupedCollection is an IEnumerable<KeyValuePair<someClass, HashSet<myClass>>> produced by LINQ 
// myGroupedResult is a Dictionary<HashSet<myClass>, List<someClass>>

我希望结果生成一个字典,其中键为 HashSet<myClass>,值为 List<someClass>。如果我有 5 个不同的哈希集,每个哈希集都有 10 次出现 someClass,我希望字典有 5 个键,每个键的值是一个包含 10 个元素的列表。相反,我得到一个包含 50 个键的字典,每个键的值是一个包含 1 个元素的列表。

我能够解决我的问题。在这里发布答案以防其他人遇到同样的问题。

解决方案有两个步骤。首先创建一个通用的IEqualityComparer<HashSet<T>>(来自):

   public class HashSetEqualityComparerBySetEquals<T> : IEqualityComparer<HashSet<T>>
   {
      public bool Equals(HashSet<T> x, HashSet<T> y)
      {
         if (ReferenceEquals(x, null))
            return false;

         return x.SetEquals(y);
      }

      public int GetHashCode(HashSet<T> set)
      {
         int hashCode = 0;

         if (set != null)
         {
            foreach (T t in set)
            {
               hashCode = hashCode ^
                   (set.Comparer.GetHashCode(t) & 0x7FFFFFFF);
            }
         }

         return hashCode;
      }    
   } 

然后在 GroupBy() 中提供它(提示来自这里:,它适用于 List,但不适用于 HashSet,它需要额外的 elementSelector 作为第二个参数):

HashSetEqualityComparerBySetEquals<myClass> comparer = new HashSetEqualityComparerBySetEquals<myClass>();

var myGroupedResult = myUngroupedCollection.
       GroupBy(x => x.Value, x => x.Key, comparer).
       ToDictionary(x => x.Key, x => x.ToList());

// myUngroupedCollection is an IEnumerable<KeyValuePair<someClass, HashSet<myClass>>> produced by LINQ but could be a Dictionary or another collection.
// myGroupedResult is a Dictionary<HashSet<myClass>, List<someClass>>

在执行其他检查相等性的 LINQ 操作时,也可以使用相同的 IEqualityComparer,例如 Distinct()FirstOrDefault():

var thisWorksAsExpected = myGroupedResult.FirstOrDefault(x => comparer.Equals(x.Key, aHashSetWithSameElements));
var thisAlsoWorks = myGroupedResult.FirstOrDefault(x => x.Key.SetEquals(aHashSetWithSameElements));

var thisDoesNotWork = myGroupedResult.FirstOrDefault(x => x.Key == aHashSetWithSameElements);
// thisDoesNotWork returns null sometimes even when all elements match