Хеширование массива в С#

Короткий вопрос

Как реализовать GetHashCode для Array.

Подробнее

У меня есть объект, который переопределяет Equals, проверяя, что:

this.array[n] == otherObject.array[n]

для всех n в Array.

Естественно, я должен реализовать дополнительный GetHashCode. Мне было интересно, есть ли способ .NET для этого, или если я должен реализовать свое собственное, что-то вроде

hash = hash ^ array[n]

Разъяснение

Мой объект содержит массив, и меня интересует GetHashCode для элементов массива. Например, мой код для эквивалентности массива - например, как и мой вопрос, но, возможно, я не был ясен, меня интересует GetHashCode (not Equals). Я говорю, что я, естественно, должен реализовать дополняющий GetHashCode, потому что это требование .NET для реализации этого после того, как Equals будет переопределено (для Dictionary и т.д. Для правильной работы). Спасибо.

Ответ 1

Чтобы вычислить хеш-код с использованием элементов массива, вы можете привести массив к IStructuralEquatable, а затем вызвать метод GetHashCode (IEqualityComparer), передавая компаратор для типа элементов в массиве.

(Приведение необходимо, потому что класс Array реализует метод явно.)

Например, если у вашего объекта есть массив int, вы можете реализовать GetHashCode следующим образом:

public override int GetHashCode()
{
    return ((IStructuralEquatable)this.array).GetHashCode(EqualityComparer<int>.Default);
}

Если вам интересно, вот как класс Array реализует метод GetHashCode (из ссылочного источника):

internal static int CombineHashCodes(int h1, int h2) {
    return (((h1 << 5) + h1) ^ h2);
}

int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
    if (comparer == null)
        throw new ArgumentNullException("comparer");
    Contract.EndContractBlock();

    int ret = 0;

    for (int i = (this.Length >= 8 ? this.Length - 8 : 0); i < this.Length; i++) {
        ret = CombineHashCodes(ret, comparer.GetHashCode(GetValue(i)));
    }

    return ret;
}

Как видите, текущая реализация использует только последние восемь элементов массива.

Ответ 2

Я не согласен, что вы должны естественным образом реализовать GetHashCode в массиве
Вам нужно будет обновить его при каждом изменении
Или рассчитать его на лету
Я бы сравнил прямо на лету SequenceEquals будет использовать сопоставитель равенства по умолчанию, поэтому вы также должны реализовать

public bool Equals

В объектах в массив

Enumerable.SequenceEqual
Имеет пример

public static void SequenceEqualEx1()
{
    Pet pet1 = new Pet { Name = "Turbo", Age = 2 };
    Pet pet2 = new Pet { Name = "Peanut", Age = 8 };

    // Create two lists of pets.
    List<Pet> pets1 = new List<Pet> { pet1, pet2 };
    List<Pet> pets2 = new List<Pet> { pet1, pet2 };

    bool equal = pets1.SequenceEqual(pets2);

    Console.WriteLine(
        "The lists {0} equal.",
        equal ? "are" : "are not");
}

Ответ 3

Это зависит от того, что вы хотите...

Один из вариантов, как Майкл ответил выше, - это иметь хеш-код, основанный на элементах массива. Это будет соответствовать вашей семантике значения Equals. Однако, поскольку "в качестве ориентира хэш объекта должен быть одинаковым в течение всего времени жизни объекта", вы должны будете убедиться, что ваш массив не изменится после получения его хэш-кода. Наличие неизменяемого контейнера с требованием, чтобы оно никогда не менялось, звучит для меня ошибочно.

Другой (лучший вариант для IMO) - переключиться на неизменяемый контейнер (т.е. ImmutableArray), тогда имеет смысл использовать хеш-код на основе значений. Вы можете использовать IStructuralEquatable как указано выше, или более широко:

    public override GetHashCode() =>
        Value.Aggregate(0, (total, next) => HashCode.Combine(total, next));

который будет работать и для других неизменных коллекций.