Aloha,
Here’s a simple class that overrides GetHashCode:
class OverridesGetHashCode
{
public string Text { get; set; }
public override int GetHashCode()
{
return (Text != null ? Text.GetHashCode() : 0);
}
// overriding Equals() doesn't change anything, so I'll leave it out for brevity
}
When I create an instance of that class, add it to a HashSet and then change its Text property, like this:
var hashset = new HashSet<OverridesGetHashCode>();
var oghc = new OverridesGetHashCode { Text = "1" };
hashset.Add(oghc);
oghc.Text = "2";
then this doesn’t work:
var removedCount = hashset.RemoveWhere(c => ReferenceEquals(c, oghc));
// fails, nothing is removed
Assert.IsTrue(removedCount == 1);
and neither does this:
// this line works, i.e. it does find a single item matching the predicate
var existing = hashset.Single(c => ReferenceEquals(c, oghc));
// but this fails; nothing is removed again
var removed = hashset.Remove(existing);
Assert.IsTrue(removed);
I guess the hash it internally uses is generated when item is inserted and, if that’s true, it’s
understandable that hashset.Contains(oghc) doesn’t work.
I also guess it looks up item by its hash code and if it finds a match, only then it checks the predicate, and that might be why the first test fails (again, I’m just guessing here).
But why does the last test fail, I just got that object out of the hashset? Am I missing something, is this a wrong way to remove something from a HashSet?
Thank you for taking the time to read this.
UPDATE: To avoid confusion, here’s the Equals():
protected bool Equals(OverridesGetHashCode other)
{
return string.Equals(Text, other.Text);
}
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((OverridesGetHashCode) obj);
}
There are good answers here and just wanted to add this. If you look at the decompiled
HashSet<T>code, you’ll see thatAdd(value)does the following:IEqualityComparer<T>.GetHashCode()to get the hash code for value. For the default comparer this boils down toGetHashCode().When you call
Remove(value)it does steps 1. and 2. again, to find where the reference is at. Then it callsIEqualityComparer<T>.Equals()to make sure that it indeed has found the right value. However, since you’ve changed whatGetHashCode()returns, it calculates a different bucket/slot location, which is invalid. Thus, it cannot find the object.So, note that
Equals()doesn’t really come into play here, because it will never even get to the right bucket/slot location if the hash code changes.