Understanding the behavior and overriding GetHashCode ()

I tried to follow the Guide from MSDN and also mentioned this great question , but the following doesn't seem to behave as expected.

I am trying to imagine a structure similar to FQN, where as if P1 were listed before P2 , P2 would only exist in the same set as P1 . How scope works.


On the issue of GetHashCode ()

I have a class with properties like this.

class data{
   public readonly string p1, p2;
   public data(string p1, string p2) {
       this.p1 = p1;
       this.p2 = p2;
   }
   public override int GetHashCode()
   {
       return this.p1.GetHashCode() ^ this.p2.GetHashCode();
   }
  /*also show the equal for comparison*/
    public override bool Equals(System.Object obj)
    {
        if (obj == null)
            return false;
        data d = obj as data;
        if ((System.Object)d == null)
            return false;
        /*I thought this would be smart*/
        return d.ToString() == this.ToString();
    }
    public override string ToString() {
        return "[" + p1 +"][" + p2+ "]";
    }
}

      

In Dictionary

(dict), I use data

as a key, so this will make the area visible like d1.p1.p2

(or rather p2 from p1 from d1, however you prefer to represent it)

Dictionary<data,int> dict = new Dictionary<data,int>();

      

I studied the behavior when d1.p1 and other d2.p1 are different, the operation is allowed correctly. However, when d1.p1 and d2.p1 are the same and the p2 of d1 and d2 are different, I observe the following behavior.

data d1 = new data(){ p1="p1", p2="p2"  };
data d2 = new data(){ p1="p1", p2="XX"  };
dict.add(d1, 0);
dict.add(d2, 1);
dict[d1] = 4;

      

As a result, both elements 4

  • Is GetHashCode () set correctly?
  • Is Equals correct?
  • If they are both fine, how / why does this behavior happen?

On the topic of the dictionary

In a viewport (VS2013) I have a list of keywords in a dictionary, not one key per index as I would normally expect, each property of my data object is a key for one index. So I'm not sure if this is the problem, or I'm just underestimating the Watch window view as a key. I know exactly how VS will map the object, but I'm not sure what I expect it to display for the key in the dictionary.

  • I thought GetHashCode () was the main "compare" operation of the dictionary, is that always correct?
  • What's the real "Index" for a dictionary where the key is an object?

UPDATE

After looking at each hash code, I immediately noticed that they were repeating. However, the Dictionary does not determine that the index exists. Below is an example of the data I see.

1132917379      string: [ABC][ABC]   
-565659420      string: [ABC][123]  
-1936108909     string: [123][123]  
//second loop with next set of strings   
1132917379      string: [xxx][xxx]  
-565659420      string: [xxx][yyy]
//...etc

      

+3


source to share


2 answers


  • Is GetHachCode () set correctly?

Of course, for some definition "correct". It cannot be overridden well, but it is not a flawed implementation (since two instances of a class that are considered equal will have a hash with the same value). Of course, with this requirement, you can always just return 0 from GetHashCode

and that will be "correct". This, of course, would not be good.

However, your specific implementation is not as good as it could be. For example, in your class, the order of the lines matters. That is new data( "A", "B" ) != new data( "B", "A" )

. However, they will always be hash equal because your implementation is GetHashCode

symmetric. Better to break the symmetry in some way. For example:

public int GetHashCode()
{
    return p1.GetHashCode() ^ ( 13 * p2.GetHashCode() );
}

      

Now it is less likely that there will be a collision for two instances that are not equal.

  • Is it correctly redefined correctly?

Well it can definitely be improved. For example, the first null check is redundant, and thus the cast to object

is performed in the second comparison. The whole thing would probably be better written as:

public bool Equals( object obj )
{
    var other = obj as data;
    if( other == null ) return false;
    return p1 == obj.p1 && p2 == obj.p2;
}

      

I also removed the call ToString

as it doesn't make the code much simpler or more readable. This is also an inefficient way of doing the comparison, as you have to build two new lines before the comparison can take place. Simply comparing members directly gives you more options to get started with and, more importantly, is much easier to read (the actual implementation of equality is independent of the string representation).



  • If they are both fine, how / why does this behavior happen?

I don't know, because the code you provided won't do it. It also won't compile. Your class data

has two fields readonly

, you cannot assign them using an initializer list as shown in the last code snippet.

I can only guess about the behavior you are seeing because nothing you have shown here will result in the described behavior.

The best advice I can give is to make sure your key class is not changed. Variable types won't work well withDictionary

. The class Dictionary

does not expect the hash codes of the objects to change, so if it GetHashCode

depends on any part of your class that has changed, it is very possible that things can get very confusing.

  • I thought GetHachCode () was the main dictionary comparison operation, is that always correct?

Dictionary

uses only GetHashCode

as a way to "address" objects (a specific hash code is used to determine which bucket an item should be placed in). He does not use it directly as a comparison. And if so, he can only use it to determine if two objects are not equal, he cannot use it to determine if they are equal.

  • What's the real "pointer" for a dictionary where the key is an object?

I'm not really sure what you're asking here, but I'm inclined to say the answer is that it doesn't matter. If the subject goes, it doesn't matter. If you care about that, you probably shouldn't use Dictionary

.

+1


source


Is GetHashCode () set correctly?

Not. You allow passing null

for p1

or p2

and null.GetHashCode()

yields a NullReferenceException

, which is not allowed in GetHashCode

. Either disallow transmission null

or make GetHashCode

return int

for zeros (zero is fine).

You are also XORing unmodified ints; this means that every class you create that contains two of the same values โ€‹โ€‹will have a hashCode of zero. This is a very common encounter; usually one multiplies each hash code by a prime number to avoid this.

Is Equals correct?



Not. The page you linked to is not a shared Equals

one System.Collections.HashTable

. You are using generic System.Collections.Generic.Dictionary

, which is using generic IEquatable<T>

. You need to implement IEquatable<data>

as described in the accepted answer to the SO question you posted.

It is true that it IEquatable<data>

will Equals(System.Object obj)

fall back to if not defined, but does not rely on this behavior. Also, converting ints to strings to compare them is not smart. Every time you feel like you should write a comment that excuses something as โ€œsmart,โ€ you are making a mistake.

A better implementation of "data" would be:

public class MatPair : IEquatable<MatPair>
{
    public readonly string MatNeedsToExplainWhatThisRepresents;
    public readonly string MatNeedsToExplainThisToo;

    public MatPair(string matNeedsToExplainWhatThisRepresents,
        string matNeedsToExplainThisToo)
    {
        if (matNeedsToExplainWhatThisRepresents == null) throw new ArgumentNullException("matNeedsToExplainWhatThisRepresents");
        if (matNeedsToExplainThisToo == null) throw new ArgumentNullException("matNeedsToExplainThisToo");

        this.MatNeedsToExplainWhatThisRepresents = matNeedsToExplainWhatThisRepresents;
        this.MatNeedsToExplainThisToo = matNeedsToExplainThisToo;
    }

    [Obsolete]
    public override bool Equals(object obj)
    {
        return Equals(obj as MatPair);
    }

    public bool Equals(MatPair matPair)
    {
        return matPair != null
               && matPair.MatNeedsToExplainWhatThisRepresents == MatNeedsToExplainWhatThisRepresents
               && matPair.MatNeedsToExplainThisToo == MatNeedsToExplainThisToo;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return MatNeedsToExplainWhatThisRepresents.GetHashCode() * 31
                ^ MatNeedsToExplainThisToo.GetHashCode();
        }
    }

    public override string ToString()
    {
        return "{" + MatNeedsToExplainWhatThisRepresents + ", "
            + MatNeedsToExplainThisToo + "}";
    }
}

      

0


source







All Articles