Метод расширения GetHashCode

Прочитав все вопросы и ответы по 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

Это похоже на надежный способ сделать это.

Мое единственное предложение состоит в том, что если вы действительно обеспокоены его производительностью, вы можете захотеть добавить общие версии для нескольких распространенных случаев (то есть, вероятно, 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;
}

Ответ 2

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

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

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

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

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

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

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

    // --------------------------------------------------------------------
    // 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;
        }
    ...
    }

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

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

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

Недостатки:

  • Overkill, если вы не делаете много поиска/вызовов в словаре GetHashCode().

Ответ 7

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

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

Ответ 8

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

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

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

Ответ 9

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

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

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

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

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

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