Search code examples
c#gethashcode

Implementing equality for 2d lines


I have simple class defining 2d line:

public class Line {
    public double X1 { get; set; }
    public double Y1 { get; set; }
    public double X2 { get; set; }
    public double Y2 { get; set; }
}

My primary goal is to get different lines from List using .Distinct(). In my case two lines are equal if theirs coordinates are equal regardless direction (line 1,2 -> 3,4 equals to 3,4 -> 1,2). I going to implement Equals like:

public override bool Equals(object obj) {
    if (obj as Line == null) { return false; }
    var second = (Line)obj;
    if (this.X1 != second.X1 && this.X1 != second.X2) { return false; }
    if (this.Y1 != second.Y1 && this.Y1 != second.Y2) { return false; }
    if (this.X2 != second.X2 && this.X2 != second.X1) { return false; }
    if (this.Y2 != second.Y2 && this.Y2 != second.Y1) { return false; }
    return true;
}

but I have no idea how to implement GetHashCode (as I understand it's necessary to make it in case of using Distinct())


Solution

  • This becomes a bit easier if you first define a Point, then define your Line in terms of 2 Points.

    Now, to calculate a reliable (but unaffected by direction) hash of a Line, make sure you order your points consistently when calculating the hash.

    Putting this all together into a complete implementation (which also covers operator == and !=):

    public class Point
    {
        public double X { get; set; }
        public double Y { get; set; }
    
        protected bool Equals(Point other)
        {
            return X.Equals(other.X) && Y.Equals(other.Y);
        }
    
        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);
        }
    
        public override int GetHashCode()
        {
            unchecked
            {
                return (X.GetHashCode()*397) + Y.GetHashCode();
            }
        }
    
        public static bool operator ==(Point left, Point right)
        {
            return Equals(left, right);
        }
    
        public static bool operator !=(Point left, Point right)
        {
            return !Equals(left, right);
        }
    }
    
    public class Line
    {
        public Point Point1 { get; set; }
        public Point Point2 { get; set; }
    
        protected bool Equals(Line other)
        {
            return Equals(Point1, other.Point1) && Equals(Point2, other.Point2) 
                 || Equals(Point1, other.Point2) && Equals(Point2, other.Point1);
        }
    
        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((Line) obj);
        }
    
        public override int GetHashCode()
        {
            unchecked
            {
                var orderedPoints =
                    new[] {Point1, Point2}.OrderBy(p => p != null ? p.X : 0)
                                          .ThenBy(p => p != null ? p.Y : 0).ToList();
                var p1 = orderedPoints[0];
                var p2 = orderedPoints[1];
                return ((p1 != null ? p1.GetHashCode() : 0)*397) 
                       + (p2 != null ? p2.GetHashCode() : 0);
            }
        }
    
        public static bool operator ==(Line left, Line right)
        {
            return Equals(left, right);
        }
    
        public static bool operator !=(Line left, Line right)
        {
            return !Equals(left, right);
        }
    }