Вопрос

После прочтения всех вопросов и ответов в StackOverflow, касающихся переопределения GetHashCode() Я написал следующий метод расширения для легкого и удобного переопределения GetHashCode():

public static class ObjectExtensions
{
    private const int _seedPrimeNumber = 691;
    private const int _fieldPrimeNumber = 397;
    public static int GetHashCodeFromFields(this object obj, params object[] fields) {
        unchecked { //unchecked to prevent throwing overflow exception
            int hashCode = _seedPrimeNumber;
            for (int i = 0; i < fields.Length; i++)
                if (fields[i] != null)
                    hashCode *= _fieldPrimeNumber + fields[i].GetHashCode();
            return hashCode;
        }
    }
}

(По сути, я только переработал код, который кто-то там разместил, потому что мне действительно нравится, что его можно использовать в целом)

который я использую следующим образом:

    public override int GetHashCode() {
        return this.GetHashCodeFromFields(field1, field2, field3);
    }

Видите ли вы какие-либо проблемы с этим кодом?

Это было полезно?

Решение

Это выглядит как надежный способ сделать это.

Мое единственное предложение заключается в том, что если вы действительно обеспокоены производительностью с его помощью, вы можете захотеть добавить универсальные версии для нескольких распространенных случаев (т.Е.вероятно, 1-4 аргумента).Таким образом, для этих объектов (которые, скорее всего, будут небольшими составными объектами в стиле ключей) у вас не будет накладных расходов на построение массива для передачи методу, циклу, любому пакету общих значений и т.д.Синтаксис вызова будет точно таким же, но для этого случая вы запустите немного более оптимизированный код.Конечно, я бы провел несколько тестов производительности по этому поводу, прежде чем вы решите, стоит ли идти на компромисс в обслуживании.

Что- то вроде этого:

public static int GetHashCodeFromFields<T1,T2,T3,T4>(this object obj, T1 obj1, T2 obj2, T3 obj3, T4 obj4) {
    int hashCode = _seedPrimeNumber;
    if(obj1 != null)
        hashCode *= _fieldPrimeNumber + obj1.GetHashCode();
    if(obj2 != null)
        hashCode *= _fieldPrimeNumber + obj2.GetHashCode();
    if(obj3 != null)
        hashCode *= _fieldPrimeNumber + obj3.GetHashCode();
    if(obj4 != null)
        hashCode *= _fieldPrimeNumber + obj4.GetHashCode();
    return hashCode;
}

Другие советы

Некоторое время назад я написал кое-что, что могло бы помочь вам решить вашу проблему...(И на самом деле, это, вероятно, можно было бы улучшить, включив семя, которое у вас есть ...)

Во всяком случае, проект называется Essence ( http://essence.codeplex.com/ ), и он использует System.Linq.Библиотеки выражений для генерации (на основе атрибутов) стандартных представлений Equals/GetHashCode/compareTo /toString, а также возможность создавать классы IEqualityComparer и IComparer на основе списка аргументов.(У меня также есть еще несколько идей, но я хотел бы получить отзывы сообщества, прежде чем продолжать слишком долго.)

(Это означает, что это почти так же быстро, как рукописный ввод - основное, где этого нет, - compareTo();причина Linq.В версии 3.5 Expressions отсутствует понятие переменной, поэтому вам приходится дважды вызывать compareTo() для базового объекта, когда вы не получаете совпадения.Использование расширений DLR для Linq.Выражения решают эту проблему.Я полагаю, я мог бы использовать emit il, но в то время я не был настолько вдохновлен.)

Это довольно простая идея, но я раньше не видел, как это делается.

Теперь дело в том, что я отчасти потерял интерес к его доработке (что включало бы написание статьи для codeproject, документирование части кода или тому подобное), Но меня можно было бы убедить сделать это, если вы считаете, что это может представлять интерес.

(На сайте codeplex нет загружаемого пакета;просто зайдите в исходный код и возьмите это - о, это написано на f # (хотя весь тестовый код находится на c #), поскольку это было то, что мне было интересно изучить.)

В любом случае, вот пример c # из теста в проекте:

    // --------------------------------------------------------------------
    // USING THE ESSENCE LIBRARY:
    // --------------------------------------------------------------------
    [EssenceClass(UseIn = EssenceFunctions.All)]
    public class TestEssence : IEquatable<TestEssence>, IComparable<TestEssence>
    {
        [Essence(Order=0] public int MyInt           { get; set; }
        [Essence(Order=1] public string MyString     { get; set; }
        [Essence(Order=2] public DateTime MyDateTime { get; set; }

        public override int GetHashCode()                                { return Essence<TestEssence>.GetHashCodeStatic(this); }
    ...
    }

    // --------------------------------------------------------------------
    // EQUIVALENT HAND WRITTEN CODE:
    // --------------------------------------------------------------------
    public class TestManual
    {
        public int MyInt;
        public string MyString;
        public DateTime MyDateTime;

        public override int GetHashCode()
        {
            var x = MyInt.GetHashCode();
            x *= Essence<TestEssence>.HashCodeMultiplier;
            x ^= (MyString == null) ? 0 : MyString.GetHashCode();
            x *= Essence<TestEssence>.HashCodeMultiplier;
            x ^= MyDateTime.GetHashCode();
            return x;
        }
    ...
    }

В любом случае, проект, если кто-то считает его стоящим, нуждается в доработке, но идеи есть...

По-моему, я выгляжу довольно хорошо, у меня есть только одна проблема:Это позор, что вам приходится использовать object[] передавать значения, поскольку при этом будут указаны любые типы значений, которые вы отправляете в функцию.Однако я не думаю, что у вас есть большой выбор, если только вы не пойдете по пути создания некоторых общих перегрузок, как предлагали другие.

Исходя из общего принципа, вы должны охватить свой unchecked настолько узко, насколько вы разумно можете, хотя здесь это не имеет большого значения.В остальном, выглядит нормально.

public override int GetHashCode() {
    return this.GetHashCodeFromFields(field1, field2, field3, this);
}

(да, я очень педантичен, но это единственная проблема, которую я вижу)

Более оптимальный:

  1. Создайте генератор кода, который использует отражение для просмотра полей вашего бизнес-объекта и создает новый частичный класс, который переопределяет GetHashCode() (и Equals()).
  2. Запустите генератор кода при запуске вашей программы в режиме отладки, и если код изменился, завершите работу с сообщением разработчику о необходимости перекомпиляции.

Преимуществами этого являются:

  • Используя отражение, вы знаете, какие поля являются типами значений, а какие нет, и, следовательно, нуждаются ли они в проверке null.
  • Здесь нет накладных расходов - никаких дополнительных вызовов функций, никакого построения списка и т.д.Это важно, если вы часто просматриваете словарь.
  • Длинные реализации (в классах с большим количеством полей) скрыты в частичных классах, вдали от вашего важного бизнес-кода.

Недостатки:

  • Перебор, если вы не выполняете много поисков по словарю / вызовов GetHashCode().

Я должен отметить, что вы почти никогда не должны выполнять распределение при реализации GetHashCode (вот некоторые полезный Блог посты об этом).

Таким образом , что params работает (генерирует новый массив "на лету") означает, что это действительно не очень хорошее общее решение.Вам было бы лучше использовать вызов метода для каждого поля и поддерживать состояние хэша в виде передаваемой им переменной (это упрощает использование улучшенных функций хеширования, а также лавинообразного преобразования).

Помимо проблем, возникающих при использовании params object[] fields, Я думаю, что неиспользование информации о типе также может быть проблемой производительности в некоторых ситуациях.Предположим , что есть два класса A, B имеют одинаковый тип и количество полей и реализуют один и тот же интерфейс I.Теперь, если вы поставите A и B возражает против Dictionary<I, anything> объекты с одинаковыми полями и разных типов окажутся в одной корзине.Я бы, вероятно, вставил какое-нибудь утверждение вроде hashCode ^= GetType().GetHashCode();

Принятый ответ Джонатана Раппа касается массива параметров, но не касается набора типов значений.Итак, если производительность очень важна, я бы, вероятно, заявил GetHashCodeFromFields не имея возражений, но int параметры, и отправлять не сами поля, а хэш-коды полей.т. е.

public override int GetHashCode() 
{
    return this.GetHashCodeFromFields(field1.GetHashCode(), field2.GetHashCode());
}

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

hashCode *= _fieldPrimeNumber + fields[i].GetHashCode();

Я бы предложил :

hashCode = hashCode * _fieldPrimeNumber + fields[i].GetHashCode();

Или что-то подобное с xor, например это :

hashCode = hashCode * _fieldPrimeNumber ^ fields[i].GetHashCode();
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top