Search code examples
c#hashsetequalityiequalitycomparer

HashSet item can be changed into same item in Set


I have a Node class :

public class Node : INode
    {  
        public object Value { get; set; }
    }

And I have EqualityComparer for this Node class like this :

public class INodeEqualityComparer : EqualityComparer<INode>
    {
        private INodeEqualityComparer()
        {

        }

        private static readonly INodeEqualityComparer _instance = 
        new INodeEqualityComparer();

        public static INodeEqualityComparer Instance
        {
            get { return _instance; }
        }

        public override bool Equals(INode x, INode y)
        {
            return (int)(x.Value) == (int)(y.Value);
        }

        public override int GetHashCode(INode obj)
        {
            return ((int)(obj.Value)).GetHashCode();
        }
    }

I create my HashSet by passing the NodeEqualityComparer.

I have 4 Node instances :

Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
Node n4 = new Node(1);

When I add n1,n2,n3,n4 to my hashset , n4 get ignored.

HashSet<INode> nodes = new HashSet<INode>(INodeEqualityComparer.Instance);
nodes.Add(n1);
nodes.Add(n2);
nodes.Add(n3);
nodes.Add(n4);

BUT after I use this changing :

nodes.Where(n => (int)(n.Value) == 3).FirstOrDefault().Value = 1;

there will be 2 elements that are equal together (value=1) based on NodeEqualityComparer. those are n1 and n3.

Debbuger

WHY the hashset does not prevent updating node or remove it ?


Solution

  • This is by design: hashing collections (whether it's dictionaries or hash sets or any other) assume that an object's hash code does not change after it's been inserted in the collection. And since two objects that are considered equal must also have the same hash code, then it also means that whatever its Equals implementation returns must not change for the same parameter.

    This applies to what is hashed: in dictionaries, that's the key. In sets, that's the entire object.

    The .NET documentation states:

    In general, for mutable reference types, you should override GetHashCode() only if:

    • You can compute the hash code from fields that are not mutable; or

    • You can ensure that the hash code of a mutable object does not change while the object is contained in a collection that relies on its hash code.

    In your Node class, you use a mutable property (Value) to compute the hash code. That's usually a bad idea, and it's actually something that ReSharper will warn against. Then again, overriding Equals and GetHashCode normally means that you're treating the type as a "value" rather than an "entity", and values should be treated as immutable when possible.

    If you can't make your object immutable, don't store it in a hash collection.