Pergunta

Let's say we have such a class:

class MyClass
{
    public string SomeValue { get; set; }

    // ...
}

Now, let's say two MyClass instances are equal when their SomeValue property is equal. Thus, I overwrite the Object.Equals() and the Object.GetHashCode() methods to represent that. Object.GetHashCode() returns SomeValue.GetHashCode() But at the same time I need to follow these rules:

  1. If two instances of an object are equal, they should return the same hash code.
  2. The hash code should not change throughout the runtime.

But apparently, SomeValue can change, and the hash code we did get before may turn to be invalid.

I can only think of making the class immutable, but I'd like to know what others do in this case.

What do you do in such cases? Is having such a class represents a subtler problem in the design decisions?

Foi útil?

Solução

The general contract says that if A.equals(B) is true, then their hash codes must be the same. If SomeValue changes in A in such a way that A.equals(B) is no longer true, then A.GetHashCode() can return a different value than before. Mutable objects cannot cache GetHashCode(), it must be calculated every time the method is called.

This article has detailed guidelines for GetHashCode and mutability:

http://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/

Outras dicas

If your GetHashCode() depends on some mutable value you have to change your hash whenever your value changes. Otherwise you break the equals law.

The part, that a hash should never be changed, once somebody asked for it, is needed if you put your object into a HashSet or as a key within a Dictionary. In these cases you have to ensure that the hash code won't be changed as long as it is stored in such a container. This can either be ensured manually, by simply taking care of this issue when you program or you could provide some Freeze() method to your object. If this is called any subsequent try to set a property would lead to some kind of exception (also you should then provide some Defrost() method). Additionally you put the call of the Freeze() method into your GetHashCode() implementation and so you can be quite sure that nobody alter a frozen object by mistake.

And just one last tip: If you need to alter a object within such a container, simply remove it, alter it (don't forget to defrost it) and re-add it again.

You sort of need to choose between mutability and GetHashCode returning the same value for 'equal' objects. Often when you think you want to implement 'equal' for mutable objects, you end up later deciding that you have "shades of equal" and really didn't mean Object.Equals equality.

Having a mutable object as the 'key' in any sort of data structure is a big red flag to me. For example:

MyObj a = new MyObj("alpha");
MyObj b = new MyObj("beta");
HashSet<MyObj> objs = new HashSet<MyObj>();
objs.Add(a);
objs.Add(b);
// objs.Count == 2
b.SomeValue = "alpha";
// objs.Distinct().Count() == 1, objs.Count == 2

We've badly violated the contract of HashSet<T>. This is an obvious example, there are subtle ones.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top