Implementing correct GetHashCode

I have the following class

public class ResourceInfo
{
    public string Id { get; set; }
    public string Url { get; set; }
}

which contains information about some resource. Now I need the possibility to check if two such resources are equal by the following scenario (I`ve implemented IEquatable interface)

public class ResourceInfo : IEquatable<ResourceInfo>
{
    public string Id { get; set; }
    public string Url { get; set; }

    public bool Equals(ResourceInfo other)
    {
        if (other == null)
            return false;

        // Try to match by Id
        if (!string.IsNullOrEmpty(Id) && !string.IsNullOrEmpty(other.Id))
        {
            return string.Equals(Id, other.Id, StringComparison.InvariantCultureIgnoreCase); 
        }

        // Match by Url if can`t match by Id
        return string.Equals(Url, other.Url, StringComparison.InvariantCultureIgnoreCase);
    }
}

Usage: oneResource.Equals(otherResource) . And everything is just fine. But some time have passed and now I need to use such eqaulity comparing in some linq query.

As a result I need to implement separate Equality comparer which looks like this:

class ResourceInfoEqualityComparer : IEqualityComparer<ResourceInfo>
{
    public bool Equals(ResourceInfo x, ResourceInfo y)
    {
        if (x == null || y == null)
            return object.Equals(x, y);

        return x.Equals(y);
    }

    public int GetHashCode(ResourceInfo obj)
    {
        if (obj == null)
            return 0;

        return obj.GetHashCode();
    }
}

Seems to be ok: it makes some validation logic and uses the native equality comparing logic. But then I need to implement GetHashCode method in the ResourceInfo class and that is the place where I have some problem.

I don`t know how to do this correctly without changing the class itself.

At first glance, the following example can work

public override int GetHashCode()
{
    // Try to get hashcode from Id
    if(!string.IsNullOrEmpty(Id))
        return Id.GetHashCode();
    // Try to get hashcode from url
    if(!string.IsNullOrEmpty(Url))
        return Url.GetHashCode();

    // Return zero
    return 0;
}

But this implementation is not very good.

GetHashCode should match the Equals method : if two objects are equal, then they should have the same hashcode, right? But my Equals method uses two objects to compare them. Here is the usecase, where you can see the problem itself:

var resInfo1 = new ResourceInfo()
{
    Id = null,
    Url = "http://res.com/id1"
};
var resInfo2 = new ResourceInfo()
{
    Id = "id1",
    Url = "http://res.com/id1"
};

So, what will happen, when we invoke Equals method: obviously they will be equal, because Equals method will try to match them by Id and fail, then it tries matching by Url and here we have the same values. As intended.

resInfo1.Equals(resInfo1 ) -> true

But then, if they are equal, they should have the same hash codes:

var hash1 = resInfo.GetHashCode(); // -263327347
var hash2 = resInfo.GetHashCode(); // 1511443452

hash1.GetHashCode() == hash2.GetHashCode() -> false

Shortly speaking, the problem is that Equals method decides which field to use for equality comparing by looking at two different objects, while GetHashCode method have access only to one object.

Is there a way to implement it correctly or I just have to change my class to avoid such situations?

Many thanks.


Your approach to equality fundamentally breaks the specifications in Object.Equals .

In particular, consider:

var x = new ResourceInfo { Id = null, Uri = "a" };
var y = new ResourceInfo { Id = "yz", Uri = "a" };
var z = new ResourceInfo { Id = "yz", Uri = "b" };

Here, x.Equals(y) would be true, and y.Equals(z) would be true - but x.Equals(z) would be false. That is specifically prohibited in the documentation:

  • If (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true.
  • You'll need to redesign, basically.

    链接地址: http://www.djcxy.com/p/39742.html

    上一篇: 有没有办法从IComparer派生IEqualityComparer?

    下一篇: 实现正确的GetHashCode