Должен ли GetHashCode быть реализован для IEquatable<T> на изменяемых типах?
Я реализую IEquatable<T>
и мне трудно найти консенсус по GetHashCode
переопределить в изменчивом классе.
Следующие ресурсы обеспечивают реализацию где GetHashCode
будет возвращать различные значения в течение времени жизни объекта, если объект изменяется:
- /questions/9074338/suschestvuet-li-polnaya-ssyilka-na-realizatsiyu-iequatable/9074343#9074343
- https://csharp.2000things.com/tag/iequatable/
- http://broadcast.oreilly.com/2010/09/understanding-c-equality-iequa.html
Тем не менее, эта ссылка гласит, что GetHashCode
не должен быть реализован для изменчивых типов по той причине, что это может вызвать нежелательное поведение, если объект является частью коллекции (и это всегда было моим пониманием также).
Интересно, что пример MSDN реализует GetHashCode
используя только неизменные свойства, что соответствует моему пониманию. Но я не понимаю, почему другие ресурсы не охватывают это. Они просто не правы?
И если тип не имеет неизменных свойств вообще, компилятор предупреждает, что GetHashCode
отсутствует, когда я переопределить Equals(object)
, В этом случае, я должен реализовать это и просто позвонить base.GetHashCode()
или просто отключить предупреждение компилятора, или я что-то пропустил и GetHashCode
всегда должен быть переопределен и реализован? На самом деле, если совет такой GetHashCode
не должно быть реализовано для изменяемых типов, зачем беспокоиться о реализации для неизменяемых типов? Это просто, чтобы уменьшить коллизии по сравнению с по умолчанию GetHashCode
реализация, или это на самом деле добавить более ощутимую функциональность?
Подводя итог моего вопроса, моя дилемма заключается в том, что использование GetHashCode
на изменяемых объектах означает, что он может возвращать различные значения в течение времени жизни объекта, если свойства на нем изменяются. Но не использовать его означает, что выгода от сравнения объектов, которые могут быть эквивалентными, теряется, потому что она всегда будет возвращать уникальное значение, и, следовательно, коллекции всегда будут использовать Equals
для своих операций.
После того, как этот вопрос был напечатан, в поле "Похожие вопросы" появился другой Вопрос, который, кажется, касается той же темы. Ответ здесь, по-видимому, совершенно ясен в том, что в неизменяемых GetHashCode
реализация. Если их нет, просто не пишите. Dictionary<TKey, TValue>
будет по-прежнему работать правильно, хотя и не с O(1) производительностью.
3 ответа
Изменяемые классы довольно плохо работают со словарями и другими классами, использующими GetHashCode и Equals.
В описываемом вами сценарии с изменяемым объектом я предлагаю одно из следующего:
class ConstantHasCode: IEquatable<ConstantHasCode>
{
public int SomeVariable;
public virtual Equals(ConstantHasCode other)
{
return other.SomeVariable == SomeVariable;
}
public override int GetHashCode()
{
return 0;
}
}
или же
class ThrowHasCode: IEquatable<ThrowHasCode>
{
public int SomeVariable;
public virtual Equals(ThrowHasCode other)
{
return other.SomeVariable == SomeVariable;
}
public override int GetHashCode()
{
throw new ApplicationException("this class does not support GetHashCode and should not be used as a key for a dictionary");
}
}
С первым словарь работает (почти) как положено, с ухудшением производительности при поиске и вставке: в обоих случаях Equals будет вызываться для каждого элемента, уже находящегося в словаре, пока сравнение не вернет true. Вы фактически возвращаетесь к исполнению Списка
Во-вторых, это способ сказать, что программисты будут использовать ваш класс "нет, вы не можете использовать это в словаре". К сожалению, насколько мне известно, нет способа обнаружить его во время компиляции, но это не удастся при первом добавлении кода в словарь, очень вероятно, довольно рано при разработке, а не ошибка, возникающая только в рабочей среде. среда с непредсказуемым набором входных данных.
И последнее, но не менее важное: не обращайте внимания на проблему "изменчивости" и реализуйте GetHashCode, используя переменные-члены: теперь вы должны знать, что вы не можете изменять класс, когда он используется в словаре. В некоторых случаях это может быть приемлемо, в других - нет.
Все зависит от того, какого рода collection
типа ты говоришь. Для моего ответа я предполагаю, что вы говорите о Hash Table
основан collections
и в частности я буду обращаться к нему для.NET Dictionary
а также Key
расчет.
Так что лучший способ определить, что произойдет, если вы измените key
(учитывая ваш key
это класс, который делает обычай HashCode
расчет), чтобы посмотреть на источник.NET. Из источника.NET мы видим, что ваш key value pair
сейчас завернут в Entry
структура, которая несет hashcode
который был рассчитан на addition
вашей ценности. Это означает, что если вы измените HashCode
значение после этого времени вашего ключа было добавлено, он больше не сможет найти значение в dictionary
,
Код для доказательства этого:
static void Main()
{
var myKey = new MyKey { MyBusinessKey = "Ohai" };
var dic = new Dictionary<MyKey, int>();
dic.Add(myKey, 1);
Console.WriteLine(dic[myKey]);
myKey.MyBusinessKey = "Changing value";
Console.WriteLine(dic[myKey]); // Key Not Found Exception.
}
public class MyKey
{
public string MyBusinessKey { get; set; }
public override int GetHashCode()
{
return MyBusinessKey.GetHashCode();
}
}
Итак, чтобы ответить на ваш вопрос. Вы хотите иметь неизменные значения, для которых вы основываете hashcode
расчет по.
Еще один момент, hashcode
для пользовательского класса, если вы не переопределите GetHashCode
будет основываться на ссылке object
, Так что беспокойство о возвращении же hashcode
для разных объектов, которые идентичны по базовым значениям, могут быть смягчены overriding GetHashCode
метод и расчет вашего HashCode
в зависимости от ваших бизнес-ключей. Например, у вас есть два строковых свойства, для вычисления хеш-кода вы бы concat
strings
и позвоните в базу string
GetHashCode
метод. Это будет гарантировать, что вы получите то же самое hashcode
для тех же базовых значений object
,
После долгих обсуждений и прочтения других SO-ответов по этой теме, в конечном итоге именно эта справочная страница ReSharper очень хорошо суммировала ее для меня:
В документации MSDN метода GetHashCode() явно не требуется, чтобы переопределение этого метода возвращало значение, которое никогда не изменяется в течение времени жизни объекта. В частности, он говорит:
Метод GetHashCode для объекта должен последовательно возвращать один и тот же хэш-код, если нет изменения состояния объекта, определяющего возвращаемое значение метода Equals объекта.
С другой стороны, он говорит, что хеш-код не должен изменяться, по крайней мере, когда ваш объект находится в коллекции:
* Вы можете переопределить GetHashCode для неизменяемых ссылочных типов. В общем, для изменяемых ссылочных типов вы должны переопределить GetHashCode, только если:
- Вы можете вычислить хеш-код из полей, которые не являются изменяемыми; или же
- Вы можете гарантировать, что хеш-код изменяемого объекта не изменится, пока объект содержится в коллекции, которая опирается на свой хеш-код.*
Но зачем вам сначала переопределять GetHashCode()? Обычно вы будете делать это, если ваш объект будет использоваться в Hashtable, как ключ в словаре и т. Д., И довольно сложно предсказать, когда ваш объект будет добавлен в коллекцию и как долго он будет храниться. там.
Учитывая все вышесказанное, если вы хотите быть в безопасности, убедитесь, что переопределение GetHashCode() возвращает то же значение в течение времени жизни объекта. ReSharper поможет вам в этом, указав на каждое не доступное для чтения поле или свойство, не предназначенное только для получения, в вашей реализации GetHashCode(). Если возможно, ReSharper также предложит быстрые исправления, чтобы сделать этих участников доступными только для чтения / получения.
Конечно, он не предлагает, что делать, если быстрые исправления невозможны. Тем не менее, это указывает на то, что эти быстрые исправления следует использовать только "по возможности", что подразумевает, что проверка может быть отменена. В ответе Джана Паоло на это предлагается создать исключение, которое предотвратит использование класса в качестве ключа и представит себя на раннем этапе разработки, если его непреднамеренно использовали в качестве ключа.
Тем не мение, GetHashCode
используется в других обстоятельствах, например, когда экземпляр вашего объекта передается в качестве параметра для настройки фиктивного метода. Таким образом, единственно возможным вариантом является GetHashCode
использование изменяемых значений и возложение бремени на остальную часть кода, чтобы гарантировать, что объект не мутирует, пока он используется в качестве ключа, или вообще не использовать его в качестве ключа.