У меня следующий класс
public class ResourceInfo
{
public string Id { get; set; }
public string Url { get; set; }
}
который содержит информацию о каком-то ресурсе. Теперь мне нужна возможность проверить, равны ли два таких ресурса по следующему сценарию (я реализовал интерфейс IEquatable)
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);
}
}
Использование: oneResource.Equals(otherResource)
. И все просто отлично. Но прошло какое-то время, и теперь мне нужно использовать такое сравнение eqaulity в каком-то linq-запросе.
В результате мне нужно реализовать отдельный компаратор Equality, который выглядит так:
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();
}
}
Кажется, все в порядке: он создает некоторую логику проверки и использует собственную логику сравнения равенства. Но затем мне нужно реализовать метод GetHashCode в классе ResourceInfo
, и это то место, где у меня есть некоторые проблемы.
Не знаю, как это правильно сделать, не меняя сам класс.
На первый взгляд, следующий пример может работать
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;
}
Но это не очень хорошая реализация.
GetHashCode должен соответствовать методу Equals: если два объекта равны, то у них должен быть одинаковый хэш-код, верно? Но мой метод Equals использует два объекта для их сравнения. Вот пример использования, в котором вы можете увидеть саму проблему:
var resInfo1 = new ResourceInfo()
{
Id = null,
Url = "http://res.com/id1"
};
var resInfo2 = new ResourceInfo()
{
Id = "id1",
Url = "http://res.com/id1"
};
Итак, что произойдет, когда мы вызовем метод Equals: очевидно, что они будут равны, потому что метод Equals попытается сопоставить их по идентификатору и потерпит неудачу, затем он попытается сопоставить их по URL-адресу, и здесь у нас есть те же значения. Как предполагалось.
resInfo1.Equals(resInfo1 ) -> true
Но тогда, если они равны, у них должны быть одинаковые хэш-коды:
var hash1 = resInfo.GetHashCode(); // -263327347
var hash2 = resInfo.GetHashCode(); // 1511443452
hash1.GetHashCode() == hash2.GetHashCode() -> false
Короче говоря, проблема в том, что метод Equals
решает, какое поле использовать для сравнения на равенство, глядя на два разных объекта, в то время как метод GetHashCode
имеет доступ только к одному объекту.
Есть ли способ реализовать это правильно или мне просто нужно изменить свой класс, чтобы избежать таких ситуаций?
Большое спасибо.