Checking equality with a HashSet of objects

2020-04-21 05:55发布

问题:

I am trying to compare two hashsets of Definition type as EqualityComparer<T>.Default.Equals(value, oldValue). Definition is defined as follows

public class Definition
{
    public string Variable { get; set; }
    public HashSet<Location> LocationList { get; set; }

    public override bool Equals(object obj)
    {
        Definition other = obj as Definition;
        return other.Variable.Equals(this.Variable) && other.LocationList!= null &&this.LocationList != null
            && other.LocationList.Count == this.LocationList.Count
            && other.LocationList == this.LocationList;
    }

    public override int GetHashCode()
    {
        return this.Variable.GetHashCode() ^ this.LocationList.Count.GetHashCode();// ^ this.LocationList.GetHashCode();
    }
}

public class Location
{
    public int Line { get; set; }
    public int Column { get; set; }
    public int Position { get; set; }
    public string CodeTab { get; set; }
    public Location(int line, int col, int pos, string tab)
    {
        Line = line;
        Column = col;
        Position = pos;
        CodeTab = tab;
    }
    public override bool Equals(object obj)
    {
        Location other = obj as Location;
        return this.CodeTab == other.CodeTab
            && this.Position == other.Position
            && this.Column == other.Column
            && this.Line == other.Line;
    }
    public override int GetHashCode()
    {
        return this.CodeTab.GetHashCode() ^ this.Position.GetHashCode() 
            ^ this.Column.GetHashCode() ^ this.Line.GetHashCode();
    }
}

Somehow for a similar set, the result is returned as false despite all the information remaining the same. The only difference is that the position of some elements are interchanged, but I know that HashSet won't preserve or check the order while comparing. Can any one advise me on what is going wrong here?

PS: I tried uncommenting this.LocationList.GetHashCode() also, but didn't work.

回答1:

You need to create a comparer for the sets:

var setComparer = HashSet<Location>.CreateSetComparer();
return other.Variable.Equals(this.Variable) && setComparer.Equals(this.LocationList, other.LocationList);


回答2:

EqualityComparer<T>.Default will look for an object implementing IEquatable<T>. Otherwise, it will defer to an ObjectEqualityComparer, which simply checks for reference equality. This is why you're seeing false when references are compared.

What you actually want to do is explicitly implement IEquatable<Location>. Note you should really make your properties immutable for this to work properly:

public class Location : IEquatable<Location>
{       
    public Location(int line, int col, int pos, string tab)
    {
        Line = line;
        Column = col;
        Position = pos;
        CodeTab = tab;
    }

    public int Line { get; private set; }
    public int Column { get; private set; }
    public int Position { get; private set; }
    public string CodeTab { get; private set; }

    public bool Equals(Location other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return string.Equals(CodeTab, other.CodeTab) && Column == other.Column && Line == other.Line && Position == other.Position;
    }

    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((Location) obj);
    }

    public static bool operator ==(Location left, Location right)
    {
        return Equals(left, right);
    }

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

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = (CodeTab != null ? CodeTab.GetHashCode() : 0);
            hashCode = (hashCode*397) ^ Column;
            hashCode = (hashCode*397) ^ Line;
            hashCode = (hashCode*397) ^ Position;
            return hashCode;
        }
    }
}

Now if you look at the type EqualityComparer created by Default, you'll see GenericEqualityComparer<Location>:

Console.WriteLine(EqualityComparer<Location>.Default.GetType())