【问题标题】:C# collection of two points doesn't return any results两点的 C# 集合不返回任何结果
【发布时间】:2016-10-05 13:32:52
【问题描述】:

类:

public class Point : IEqualityComparer<Point>
{               
    public char HorizontalPosition { get; set; }
    public int VerticalPosition { get; set; }

    public Point(char horizontalPosition, int verticalPosition)
    {
        HorizontalPosition = char.ToUpper(horizontalPosition);
        VerticalPosition = verticalPosition;           
    }   

    public bool Equals(Point x, Point y)
    {
        return (x.VerticalPosition == y.VerticalPosition && x.HorizontalPosition == y.HorizontalPosition);
    }

    public int GetHashCode(Point obj)
    {
        return (obj.HorizontalPosition.GetHashCode() + obj.VerticalPosition.GetHashCode());
    }
}

我试图在两个集合中找到公共点(交点),但结果是空集合 - 两个元素应该在其中。为什么?我已经实现了 IEqualityComparer。我是不是做错了什么?

示例集合:

  List<Point> first = new List<Point> { new Point('a', 1), new Point('b', 2) };
  List<Point> second = new List<Point> { new Point('a', 1), new Point('b', 2) };
  List<Point> intersection = first.Intersect(second).ToList();

Intersection 是空列表,但里面应该有两个元素。

【问题讨论】:

  • 哇,文档看起来有问题。你的类型应该实现IEquatable&lt;Point&gt;,而不是IEqualityComparer&lt;Point&gt;
  • @Dennis_E 除了它不会像你期望的那样工作。原因如下:stackoverflow.com/questions/1645891/…
  • @decPL 该链接表示如果您不实施GetHashCode(),它将无法正常工作。但他有,所以我不明白为什么它不能按预期工作。
  • 如果您不覆盖 public int GetHashCode(),该链接将无法正常工作。他所做的是他为IComparable&lt;T&gt;.GetHashCode(T) 提供了一个实现,它永远不会因为其他人在他们的答案中指定的原因而被调用。如果您不信任我,请自行检查。
  • @decPL 我没有注意到它是IEqualityComparer&lt;Point&gt;.GetHashCode() 而不是object.GetHashCode()。无论如何,这将详细介绍实际实现IEquatable&lt;T&gt;。我只是在说IEqualityComparer&lt;T&gt; 而不是IEquatable&lt;T&gt; 的文档上发表评论。这句话看起来不对:“默认相等比较器 Default 用于比较实现 IEqualityComparer 泛型接口的类型的值。”

标签: c# linq iequalitycomparer


【解决方案1】:

IEqualityComparer 是一个接口,您可以提供给Intersect 方法来比较项目。默认情况下不使用它来比较任何内容。所以你的代码只是在Object中使用了内置的Equals,除非对象是同一个对象,否则它将返回false。

您必须覆盖类中的默认 Equal 和 GetHashCode 方法,或者告诉交集使用您的比较器实现。但是您不应该在数据存储类中实现比较器。

【讨论】:

    【解决方案2】:

    您应该从对象覆盖Equals 和GetHashCode:

    public class Point
    {
        public char HorizontalPosition { get; set; }
        public int VerticalPosition { get; set; }
    
        public Point(char horizontalPosition, int verticalPosition)
        {
            HorizontalPosition = char.ToUpper(horizontalPosition);
            VerticalPosition = verticalPosition;
        }
    
        public override int GetHashCode()
        {
            unchecked
            { 
                return (HorizontalPosition * 397) ^ VerticalPosition;
            }
        }
    
        protected bool Equals(Point other)
        {
            return Equals(HorizontalPosition, other.HorizontalPosition) && Equals(VerticalPosition, other.VerticalPosition);
        }
    
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((Point)obj);
        }
    }
    

    您还可以实现自定义IEqualityComparer 并将其传递给intersect:

    public class PointComparer : IEqualityComparer<Point>
    {
        public bool Equals(Point a, Point b)
        {
            return a.HorizontalPosition == b.HorizontalPosition && a.VerticalPosition == b.VerticalPosition;
        }
    
        public int GetHashCode(Point p)
        {
            unchecked
            { 
                return (p.HorizontalPosition * 397) ^ p.VerticalPosition;
            }
        }
    }
    
    // ...
    
    List<Point> intersection = first.Intersect(second, new PointComparer()).ToList();
    

    正如@decPL 在 cmets 中提到的,您还应该重新考虑您的哈希码实现。

    【讨论】:

    • 另外一点 - OP 的 GetHashCode 实现并不完美,因为它会产生很多冲突((x,y) 的哈希码 == 任何 x 和 y 的 (y,x) 的哈希码)。最好使用unchecked { return (this.HorizontalPosition * 397) ^ this.VerticalPosition; }之类的东西
    • 感谢@decPL!这是真的,应该以这种方式实施。我更新了我的答案。
    【解决方案3】:
    List<Point> first = new List<Point> { new Point('a', 1), new Point('b', 2) };
                List<Point> second = new List<Point> { new Point('a', 1), new Point('b', 2) };
                List<Point> intersection = first.Intersect(second, new PointComparer()).ToList();
    
    
    public class Point 
    {
        public char HorizontalPosition { get; set; }
        public int VerticalPosition { get; set; }
    
        public Point(char horizontalPosition, int verticalPosition)
        {
            HorizontalPosition = char.ToUpper(horizontalPosition);
            VerticalPosition = verticalPosition;
        }
    }
    
    public class PointComparer : IEqualityComparer<Point>
    {
        public bool Equals(Point x, Point y)
        {
            return (x.VerticalPosition == y.VerticalPosition && x.HorizontalPosition == y.HorizontalPosition);
        }
    
        public int GetHashCode(Point obj)
        {
            return (obj.HorizontalPosition.GetHashCode() + obj.VerticalPosition.GetHashCode());
        }
    }
    

    试试上面的例子

    【讨论】:

      【解决方案4】:

      除非指定,否则Intersect 使用EqualityComparer&lt;Point&gt;.Default,它将使用object.Equals 和object.GetHashCode 方法进行比较(它们只会检查引用是否相同);

      要使其工作,请将比较器传递给方法:

        List<Point> first = new List<Point> { new Point('a', 1), new Point('b', 2) };
        List<Point> second = new List<Point> { new Point('a', 1), new Point('b', 2) };
        List<Point> intersection = first.Intersect(second, new Point('a', 0)).ToList();
      

      虽然,理想情况下,对于 SRP,您不应该在 Point 类本身上使用比较器,因为它看起来很老套,因为它看起来像上面创建 Point 就像用于比较的逻辑类.

      来自 MSDN:

      EqualityComparer

      Intersect

      【讨论】:

        【解决方案5】:

        您应该将 Point 和 PointComparer 类分开。

        手册中有很好的例子:

        public class ProductA
        { 
            public string Name { get; set; }
            public int Code { get; set; }
        }
        
        public class ProductComparer : IEqualityComparer<ProductA>
        {
        
            public bool Equals(ProductA x, ProductA y)
            {
                //Check whether the objects are the same object. 
                if (Object.ReferenceEquals(x, y)) return true;
        
                //Check whether the products' properties are equal. 
                return x != null && y != null && x.Code.Equals(y.Code) && x.Name.Equals(y.Name);
            }
        
            public int GetHashCode(ProductA obj)
            {
                //Get hash code for the Name field if it is not null. 
                int hashProductName = obj.Name == null ? 0 : obj.Name.GetHashCode();
        
                //Get hash code for the Code field. 
                int hashProductCode = obj.Code.GetHashCode();
        
                //Calculate the hash code for the product. 
                return hashProductName ^ hashProductCode;
            }
        }
        

        https://msdn.microsoft.com/en-us/library/bb460136(v=vs.110).aspx

        【讨论】:

          【解决方案6】:

          您可以在参考资料https://referencesource.microsoft.com/中找到

          System\Linq\Enumerable.cs

              public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second) {
                          if (first == null) throw Error.ArgumentNull("first");
                          if (second == null) throw Error.ArgumentNull("second");
                          return IntersectIterator<TSource>(first, second, null);
                      }
          
          ...
              static IEnumerable<TSource> IntersectIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
                  {
                      Set<TSource> set = new Set<TSource>(comparer);
                      foreach (TSource element in second) set.Add(element);
                      foreach (TSource element in first)
                          if (set.Remove(element)) yield return element;
                  }
          ...
          // If value is in set, remove it and return true; otherwise return false
                  public bool Remove(TElement value) {
                      int hashCode = InternalGetHashCode(value);
                      int bucket = hashCode % buckets.Length;
                      int last = -1;
                      for (int i = buckets[bucket] - 1; i >= 0; last = i, i = slots[i].next) {
                          if (slots[i].hashCode == hashCode && comparer.Equals(slots[i].value, value)) {
                              if (last < 0) {
                                  buckets[bucket] = slots[i].next + 1;
                              }
                              else {
                                  slots[last].next = slots[i].next;
                              }
                              slots[i].hashCode = -1;
                              slots[i].value = default(TElement);
                              slots[i].next = freeList;
                              freeList = i;
                              return true;
                          }
                      }
                      return false;
                  }
          

          您的比较器实际上没有使用

          【讨论】:

            猜你喜欢
            • 2021-11-27
            • 1970-01-01
            • 1970-01-01
            • 2017-12-28
            • 2017-12-09
            • 2020-10-03
            • 2017-03-19
            • 2017-05-20
            • 2012-08-08
            相关资源
            最近更新 更多