📜 ⬆️ ⬇️

You compare on Equals you mean GetHashCode

In FxCop there is such a rule Override GetHashCode on overriding Equals , override GetHashCode override Equals . And so the reef is connected with this rule. In the Rule Description there is written about this but as in my opinion it is not entirely clear.

This is due to the principle of how HashTable and Dictionary work in .NET, and in order for the comparison to be performed correctly when overriding Equals, it is necessary to override GetHashCode depending on the data that is involved in the comparison. Otherwise, Equals simply will not be called, although many developers expect it to always be called. Equals is called only when GetHashCode returns the same values, which, as mentioned above, is related to the principle of operation of dictionaries and hash tables in order to resolve collisions in the hash table.

On the other hand, such incorrect conclusions are quite possibly driven by the behavior of the System.Object method whose GetHashCode method returns values ​​that do not depend on the data stored in the object and returns different hash codes for identical objects.
')
And although this behavior would seem a trifle, but often many at one time stumble upon this reef.

This common mistake can be seen by example:

There is some CustomType , in fact there are two Name and Age fields in it :

public class NamesComparer : IEqualityComparer<CustomType>
{
#region IEqualityComparer<CustomType> Members

public bool Equals(CustomType x, CustomType y)
{
return string .Equals(x.Name, y.Name);
}

public int GetHashCode(CustomType obj)
{
// ,
return obj.GetHashCode();
}

#endregion
}

[DebuggerDisplay( "Name: {Name}, age: {Age}" )]
public class CustomType : IEqualityComparer<CustomType>
{
public CustomType()
{
}

public CustomType( string name, int age)
{
Name = name;
Age = age;
}

public int Age { get ; set ; }
public string Name { get ; set ; }

public override bool Equals(CustomType x, CustomType y)
{
return string .Equals(x.Name, y.Name);
}

public override int GetHashCode(CustomType obj)
{
// ,
return obj.GetHashCode();
}
}



There are two lists:
CustomType[] customTypeShortList = new [] {
new CustomType( "reno" , 1),
new CustomType( "toyota" , 3) };

CustomType[] customTypeLongList = new [] {
new CustomType( "audi" , 5),
new CustomType( "opel" , 7),
new CustomType( "reno" , 10),
new CustomType( "subaru" , 5),
new CustomType( "toyota" , 4),
new CustomType( "nissan" , 3)};



We find their intersection through the linq operator Intersect . Since we are working with a composite non-primitive type, you will need to specify an IEqualityComparer for this type:

IEnumerable <CustomType> intersect = customTypeLongList
.Intersect(customTypeShortList, new NamesComparer());



As a result, we expect to get two values ​​in the intersect list: reno-10 and toyota - 4. But we will not get them, because GetHashCode in CustomType is implemented incorrectly . Since each object as a whole has different hash codes, the Equals method will not even be called.

In this case, the correct implementation will be the option:

public int GetHashCode(CustomType obj)
{
return obj.Name.GetHashCode();
}



and you can try this option, it is certainly not correct, but the intersection will work correctly, since Equals will always be called.

public int GetHashCode(CustomType obj)
{
// , -
return string .Empty.GetHashCode();
}




Therefore, when implementing Equals , it is imperative to implement GetHashCode and taking into account the data that are involved in the comparison in Equals. Otherwise, Equals may not even be called.

Since a lot of people come across this pitfall, I decided to write about it.

From plsc_Rover comments :
It is also important not to confuse the implementation of GetHashCode from IEqualityComparer, and not redefining the GetHashCode object, since in the second case, the dictionary will not work correctly.

Source: https://habr.com/ru/post/82147/


All Articles