Должен ли GetHashCode быть реализован для IEquatable<T> на изменяемых типах?

Я реализую IEquatable<T>и мне трудно найти консенсус по GetHashCode переопределить в изменчивом классе.

Следующие ресурсы обеспечивают реализацию где GetHashCode будет возвращать различные значения в течение времени жизни объекта, если объект изменяется:

Тем не менее, эта ссылка гласит, что 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();
        }
    }

Ссылка на источник.NET.

Итак, чтобы ответить на ваш вопрос. Вы хотите иметь неизменные значения, для которых вы основываете hashcode расчет по.

Еще один момент, hashcode для пользовательского класса, если вы не переопределите GetHashCode будет основываться на ссылке object, Так что беспокойство о возвращении же hashcode для разных объектов, которые идентичны по базовым значениям, могут быть смягчены overriding GetHashCode метод и расчет вашего HashCode в зависимости от ваших бизнес-ключей. Например, у вас есть два строковых свойства, для вычисления хеш-кода вы бы concatstrings и позвоните в базу stringGetHashCode метод. Это будет гарантировать, что вы получите то же самое hashcode для тех же базовых значений object,

После долгих обсуждений и прочтения других SO-ответов по этой теме, в конечном итоге именно эта справочная страница ReSharper очень хорошо суммировала ее для меня:

В документации MSDN метода GetHashCode() явно не требуется, чтобы переопределение этого метода возвращало значение, которое никогда не изменяется в течение времени жизни объекта. В частности, он говорит:

Метод GetHashCode для объекта должен последовательно возвращать один и тот же хэш-код, если нет изменения состояния объекта, определяющего возвращаемое значение метода Equals объекта.

С другой стороны, он говорит, что хеш-код не должен изменяться, по крайней мере, когда ваш объект находится в коллекции:

* Вы можете переопределить GetHashCode для неизменяемых ссылочных типов. В общем, для изменяемых ссылочных типов вы должны переопределить GetHashCode, только если:

  • Вы можете вычислить хеш-код из полей, которые не являются изменяемыми; или же
  • Вы можете гарантировать, что хеш-код изменяемого объекта не изменится, пока объект содержится в коллекции, которая опирается на свой хеш-код.*

Но зачем вам сначала переопределять GetHashCode()? Обычно вы будете делать это, если ваш объект будет использоваться в Hashtable, как ключ в словаре и т. Д., И довольно сложно предсказать, когда ваш объект будет добавлен в коллекцию и как долго он будет храниться. там.

Учитывая все вышесказанное, если вы хотите быть в безопасности, убедитесь, что переопределение GetHashCode() возвращает то же значение в течение времени жизни объекта. ReSharper поможет вам в этом, указав на каждое не доступное для чтения поле или свойство, не предназначенное только для получения, в вашей реализации GetHashCode(). Если возможно, ReSharper также предложит быстрые исправления, чтобы сделать этих участников доступными только для чтения / получения.

Конечно, он не предлагает, что делать, если быстрые исправления невозможны. Тем не менее, это указывает на то, что эти быстрые исправления следует использовать только "по возможности", что подразумевает, что проверка может быть отменена. В ответе Джана Паоло на это предлагается создать исключение, которое предотвратит использование класса в качестве ключа и представит себя на раннем этапе разработки, если его непреднамеренно использовали в качестве ключа.

Тем не мение, GetHashCode используется в других обстоятельствах, например, когда экземпляр вашего объекта передается в качестве параметра для настройки фиктивного метода. Таким образом, единственно возможным вариантом является GetHashCode использование изменяемых значений и возложение бремени на остальную часть кода, чтобы гарантировать, что объект не мутирует, пока он используется в качестве ключа, или вообще не использовать его в качестве ключа.

Другие вопросы по тегам