Реализовать GetHashCode на класс, который имеет подстановочный знак Equatability

предположим, я хочу иметь возможность сравнивать 2 списка ints и рассматривать одно конкретное значение как дикую карту.

например, Если -1-это wild card, то

{1,2,3,4} == {1,2,-1,4} //returns true

и я пишу класс, чтобы обернуть всю эту логику, поэтому он реализует IEquatable и имеет соответствующую логику в public override bool Equals()

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

только я не вижу как я могу реализовать .GetHashCode() не нарушая свой контракт (объекты, которые равны, имеют разные хэши), или просто имея реализацию return 1.

мысли?

9 ответов


реализация Equals уже недействительно, так как это не транзитивное. Тебе, наверное, лучше уйти Equals с реализацией по умолчанию, и писать новый метод, как WildcardEquals (как указано в другие ответы здесь).

в общем, всякий раз, когда вы изменили Equals, вы должны выполнить GetHashCode если вы хотите иметь возможность хранить объекты в хэш-таблице (например, a Dictionary<TKey, TValue>) и он работает правильно. Если вы точно знаете, что объекты будут никогда не заканчивайте в хэш-таблице, тогда это теоретически необязательно (но было бы безопаснее и яснее в этом случае переопределить его, чтобы бросить"NotSupportedException" или всегда вернуть 0).

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


в этом случае я бы создал новый метод или метод расширения,WildcardEquals(other), вместо использования операторов.

Я бы не рекомендовал скрывать такую сложность.


С логической точки зрения, мы нарушаем принцип равенства. Она больше не транзитивна. Так, в случае маски, A==B и B==C не означает, что A==C.

из технической точки зрения, возвращая то же значение из GetHashCode() - это не somenting непростительно.


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

public override int GetHashCode()
{
    return this.Length.GetHashCode()
}

Это рекомендовано, но не обязательно. Если вам не нужна эта пользовательская реализация GetHashCode, просто не делай этого.


GetHashCode обычно важно, только если вы собираетесь хранить элементы своего класса в какой-то коллекции, такой как набор. Если это так, то я не думаю, что вы сможете достичь согласованной семантики, поскольку, как указывает @AlexD, равенство больше не является транзитивным.

например, (используя строковые глобусы, а не целочисленные списки) если вы добавите строки "A", " B " и " * " в набор, ваш набор будет иметь один или два элемента в зависимости от порядок их добавления.

Если это не то, что вы хотите, я бы рекомендовал поместить подстановочный знак в новый метод (например,EquivalentTo()) вместо перегрузки равенства.


наличие GetHashCode () всегда возвращает константу-единственный "законный" способ выполнения ограничения equals/hashcode.

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

Я думаю, что это единственный возможный допустимый вариант. Хэш-коды по существу существуют как ключи, чтобы быстро искать вещи, и поскольку ваш подстановочный знак должен соответствовать каждому item, его ключ для поиска должен равняться ключу каждого элемента, поэтому все они должны быть одинаковыми.

Как отмечали другие, это не то, для чего обычно используется equals, и нарушает предположения, которые многие другие вещи могут использовать для равных (например, транзитивность - EDIT: оказывается, это на самом деле договорное требование, поэтому нет), поэтому определенно стоит, по крайней мере, рассмотреть возможность сравнения их вручную или с явно отдельным компаратором равенства.


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

тем не менее, убедитесь, что вы не используете свой класс в местах, которые могут использовать GetHashCode. Это может привести вас к нагрузке проблем и быть трудно отладить, если вы не следите за этим.


предполагается, что Equals(Object) и IEquatable<T>.Equals(T) следует реализовать отношения эквивалентности, такие, что если x наблюдается равным Y, а Y наблюдается равным Z, и ни один из элементов не был изменен, X можно считать равным Z; кроме того, если X равен Y и Y делает не равно Z, тогда X также можно считать не равным Z. Методы Wild-card и нечеткого сравнения не реализуют отношения эквивалентности, и, следовательно,Equals должны, как правило, не реализовываться с такой семантикой.

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

например, если единственный подстановочный знак, который поддерживает конкретная строка, представляет собой "произвольную строку из одной или нескольких цифр", можно хэшировать строку, Преобразуя все последовательности последовательных цифр и/или строковых символов подстановки в одну" строку цифр " подстановочный знак. Если # представляет любую цифру, то строки abc123, abc#, abc456 и abc#93#22#7 будут хэшироваться до того же значения, что и abc#, но abc#b, abc123b и т. д. может хеш различное значение. В зависимости от распределения строк такие различия могут давать или не давать лучшую производительность, чем возврат постоянного значения.

обратите внимание, что даже если один реализует GetHashCode таким образом, что равные объекты дают равные хэши, некоторые коллекции могут по-прежнему вести себя странно, если метод равенства не реализует отношение эквивалентности. Например, если коллекция foo содержит элементы с ключами "abc1" и "abc2", попытки доступа foo["abc#"] может произвольно возвращаем первый пункт или второй. Попытки удалить ключ "abc#" могут произвольно удалить один или оба элемента или могут завершиться неудачей после удаления одного элемента (ожидаемое условие post не будет выполнено, так как abc# будет в коллекции даже после удаления).

вместо того, чтобы пытаться сглазить Equals чтобы сравнить равенство хэш-кода, альтернативный подход должен иметь словарь, который содержит для каждой возможной строки подстановочного знака, которая будет соответствовать хотя бы одной основной коллекции строка список строк, которые могут совпадать. Таким образом, если есть много строк, которые будут соответствовать abc#, все они могут иметь разные хэш-коды; если пользователь вводит "abc#" в качестве поискового запроса, система будет искать "abc#" в словаре диких карт и получать список всех строк, соответствующих этому шаблону, который затем может быть просмотрен индивидуально в основном словаре.