Упрощение переопределения Equals(), GetHashCode() в C# для лучшей управляемости
Я нахожу свое превосходство над собой Equals()
а также GetHashCode()
часто для реализации семантики, что бизнес-объекты с одинаковыми значениями свойств равны. Это приводит к тому, что код является повторяющимся для записи и хрупким для поддержки (свойство добавляется, и одно / оба переопределения не обновляются).
Код в конечном итоге выглядит примерно так (комментарии к реализации приветствуются):
public override bool Equals(object obj)
{
if (object.ReferenceEquals(this, obj)) return true;
MyDerived other = obj as MyDerived;
if (other == null) return false;
bool baseEquals = base.Equals((MyBase)other);
return (baseEquals &&
this.MyIntProp == other.MyIntProp &&
this.MyStringProp == other.MyStringProp &&
this.MyCollectionProp.IsEquivalentTo(other.MyCollectionProp) && // See http://stackru.com/a/9658866/141172
this.MyContainedClass.Equals(other.MyContainedClass));
}
public override int GetHashCode()
{
int hashOfMyCollectionProp = 0;
// http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/
// BUT... is it worth the extra math given that elem.GetHashCode() should be well-distributed?
int bitSpreader = 31;
foreach (var elem in MyCollectionProp)
{
hashOfMyCollectionProp = spreader * elem.GetHashCode();
bitSpreader *= 31;
}
return base.GetHashCode() ^ // ^ is a good combiner IF the combined values are well distributed
MyIntProp.GetHashCode() ^
(MyStringProp == null ? 0 : MyStringProp.GetHashValue()) ^
(MyContainedClass == null ? 0 : MyContainedClass.GetHashValue()) ^
hashOfMyCollectionProp;
}
Мои вопросы
- Является ли шаблон реализации обоснованным?
- Является ли ^ адекватным, учитывая, что значения составляющих компонентов хорошо распределены? Нужно ли умножать на 31-на-N при объединении элементов коллекции, учитывая, что их хэш хорошо распределен?
- Кажется, этот код можно абстрагировать в код, который использует отражение для определения общедоступных свойств, создает дерево выражений, которое соответствует решению, написанному вручную, и выполняет дерево выражений по мере необходимости. Этот подход кажется разумным? Есть ли где-нибудь существующая реализация?
3 ответа
MSDN на самом деле не говорит "не перегружайте Equals et al для изменяемых типов". Раньше это говорило, но теперь говорит:
Когда вы определяете класс или структуру, вы решаете, имеет ли смысл создавать пользовательское определение равенства (или эквивалентности) значений для типа. Как правило, вы реализуете равенство значений, когда ожидается, что объекты этого типа будут добавлены в коллекцию какого-либо рода, или когда их основная цель заключается в хранении набора полей или свойств.
http://msdn.microsoft.com/en-us/library/dd183755.aspx
Тем не менее, существуют сложности, связанные со стабильностью хеш-кода, в то время как объект участвует в хешированной коллекции (Dictionary<T,U>
, HashSet<T>
, так далее.).
Я решил выбрать лучшее из обоих миров, как указано здесь:
Я часто нахожу себя переопределяющим Equals() и GetHashCode()
- MSDN говорит: не перегружайте Equals et al для изменяемых типов
Является ли ^ адекватным, учитывая, что значения составляющих компонентов хорошо распределены?
- Да, но они не всегда хорошо распределены. Рассматривать
int
свойства. Рекомендуется сдвигать с некоторыми (маленькими) простыми числами.
Возможно, я запутался здесь, но не стоит ли null
проверьте возврат 1 вместо 0 в GetHashCode
переопределить?
Так
MyStringProp == null ? 0 : MyStringProp.GetHashValue()
должно быть
MyStringProp == null ? 1 : MyStringProp.GetHashValue()