IEqualityComparer для объектов значений
-
21-09-2019 - |
Вопрос
У меня есть неизменяемый объект значения IPathwayModule, значение которого определяется:
- (int) Блокировать;
- (Entity) Модуль, идентифицируемый (строкой) ModuleId;
- (перечисление) Статус;и
- (сущность) Класс, идентифицируемый (строкой) ClassId, который может быть нулевым.
Вот моя текущая реализация IEqualityComparer, которая, похоже, работает в нескольких модульных тестах.Однако мне кажется, что я недостаточно хорошо понимаю, что делаю, чтобы понять, правильно ли я это делаю.Предыдущая реализация иногда давала сбой при повторных тестовых запусках.
private class StandardPathwayModuleComparer : IEqualityComparer<IPathwayModule>
{
public bool Equals(IPathwayModule x, IPathwayModule y)
{
int hx = GetHashCode(x);
int hy = GetHashCode(y);
return hx == hy;
}
public int GetHashCode(IPathwayModule obj)
{
int h;
if (obj.Class != null)
{
h = obj.Block.GetHashCode() + obj.Module.ModuleId.GetHashCode() + obj.Status.GetHashCode() + obj.Class.ClassId.GetHashCode();
}
else
{
h = obj.Block.GetHashCode() + obj.Module.ModuleId.GetHashCode() + obj.Status.GetHashCode() + "NOCLASS".GetHashCode();
}
return h;
}
}
IPathwayModule определенно является неизменяемым, и разные экземпляры с одинаковыми значениями должны быть равны и создавать один и тот же HashCode, поскольку они используются как элементы в HashSets.
Полагаю, мои вопросы:
- Правильно ли я использую интерфейс в этом случае?
- Есть ли случаи, когда я могу не увидеть желаемого поведения?
- Есть ли способ улучшить надежность и производительность?
- Есть ли какие-то передовые практики, которым я не следую?
Решение 7
Спасибо всем, кто ответил.Я собрал отзывы всех, кто ответил, и улучшил свои результаты. IEqualityComparer
теперь выглядит так:
private class StandardPathwayModuleComparer : IEqualityComparer<IPathwayModule>
{
public bool Equals(IPathwayModule x, IPathwayModule y)
{
if (x == y) return true;
if (x == null || y == null) return false;
if ((x.Class == null) ^ (y.Class == null)) return false;
if (x.Class == null) //and implicitly y.Class == null
{
return x.Block.Equals(y.Block) && x.Status.Equals(y.Status) && x.Module.ModuleId.Equals(y.Module.ModuleId);
}
return x.Block.Equals(y.Block) && x.Status.Equals(y.Status) && x.Module.ModuleId.Equals(y.Module.ModuleId) && x.Class.ClassId.Equals(y.Class.ClassId);
}
public int GetHashCode(IPathwayModule obj)
{
unchecked {
int h = obj.Block ^ obj.Module.ModuleId.GetHashCode() ^ (int) obj.Status;
if (obj.Class != null)
{
h ^= obj.Class.ClassId.GetHashCode();
}
return h;
}
}
}
Другие советы
Не делайте Equals с точки зрения результатов хэш-функции, это слишком хрупко.Вместо этого выполните сравнение значений полей для каждого из полей.Что-то вроде:
return x != null && y != null && x.Name.Equals(y.Name) && x.Type.Equals(y.Type) ...
Кроме того, результаты хэш-функций на самом деле не поддаются сложению.Попробуйте использовать ^
вместо этого оператор.
return obj.Name.GetHashCode() ^ obj.Type.GetHashCode() ...
Вам не нужна проверка на ноль в GetHashCode.Если это значение равно нулю, у вас есть более серьезные проблемы, бесполезно пытаться восстановиться после того, что вы не можете контролировать...
Единственная большая проблема — реализация Equals.Хэш-коды не уникальны, вы можете получить один и тот же хеш-код для разных объектов.Вам следует сравнивать каждое поле IPathwayModule индивидуально.
GetHashCode() можно немного улучшить.Вам не нужно вызывать GetHashCode() для целого числа.Int сам по себе является хорошим хеш-кодом.То же самое для значений перечисления.Затем ваш GetHashCode можно реализовать следующим образом:
public int GetHashCode(IPathwayModule obj)
{
unchecked {
int h = obj.Block + obj.Module.ModeleId.GetHashCode() + (int) obj.Status;
if (obj.class != null)
h += obj.Class.ClassId.GetHashCode();
return h;
}
}
Блок «непроверенный» необходим, поскольку при арифметических операциях могут возникнуть переполнения.
Не следует использовать GetHashCode() в качестве основного способа сравнения объектов.Сравните это по полю.
Может существовать несколько объектов с одним и тем же хеш-кодом (это называется «коллизиями хеш-кода»).
Кроме того, будьте осторожны при сложении нескольких целочисленных значений, поскольку вы можете легко вызвать исключение OverflowException.Используйте «эксклюзивное или» (^), чтобы объединить хэш-коды или обернуть код в «непроверенный» блок.
Вам следует реализовать лучшие версии Equals и GetHashCode.
Например, хэш-код перечислений — это просто их числовое значение.
Другими словами, с этими двумя перечислениями:
public enum A { x, y, z }
public enum B { k, l, m }
Затем с вашей реализацией следующий тип значения:
public struct AB {
public A;
public B;
}
следующие два значения будут считаться равными:
AB ab1 = new AB { A = A.x, B = B.m };
AB ab2 = new AB { A = A.z, B = B.k };
Я предполагаю, что ты этого не хочешь.
Кроме того, передача типов значений в качестве интерфейсов приведет к их упаковке, что может привести к проблемам с производительностью, хотя, вероятно, не так уж и сильно.Вы можете подумать о том, чтобы реализация IEqualityComparer напрямую принимала ваши типы значений.
- Предполагать, что два объекта равны, потому что их хэш-код равен, неверно.Вам нужно сравнить всех участников индивидуально
- Вероятно, лучше использовать ^, а не + для объединения хеш-кодов.
Если я вас хорошо понимаю, вы хотели бы услышать некоторые комментарии по вашему коду.Вот мои замечания:
GetHashCode
должны быть объединены с помощью XOR, а не добавлены.исключающее ИЛИ (^
) дает больше шансов предотвратить столкновения- Вы сравниваете хеш-коды.Это хорошо, но делайте это только в том случае, если базовый объект переопределяет
GetHashCode
.Если нет, используйте свойства и их хэш-коды и объедините их. - Хэш-коды важны, они делают возможным быстрое сравнение.Но если хэш-коды равны, объект все равно может быть другим.Это бывает редко.Но вам нужно будет сравнить поля вашего объекта, если хеш-коды равны.
- Вы говорите, что ваши типы значений неизменяемы, но вы ссылаетесь на объекты (
.Class
), которые не являются неизменяемыми - Всегда оптимизируйте сравнение, добавляя эталонное сравнение в качестве первого теста.Ссылки неравны, объекты неравны, значит, структуры неравны.
Пункт 5 зависит от того, хотите ли вы, чтобы объекты, на которые вы ссылаетесь в своем типе значения, возвращали не равные, если не одна и та же ссылка.
РЕДАКТИРОВАТЬ: вы сравниваете много строк.Сравнение строк оптимизировано в C#.Вы можете, как предлагали другие, лучше использовать ==
с ними в вашем сравнении.Для GetHashCode используйте ИЛИ ^
как предлагали и другие.