Должен ли я использовать конкатенацию моих строковых полей в качестве хэш-кода?
-
12-09-2019 - |
Вопрос
У меня есть класс адресов в C #, который выглядит следующим образом:
public class Address
{
public string StreetAddress { get; set; }
public string RuralRoute { get; set; }
public string City { get; set; }
public string Province { get; set; }
public string Country { get; set; }
public string PostalCode { get; set; }
}
Я реализую равенство, и поэтому мне нужно переопределить хэш-код.Сначала я собирался использовать формулу хэш-кода из EJ, но потом подумал:Это все строковые поля, разве я не могу просто использовать StringBuilder для их объединения и возврата хэш-кода из этой строки?
Это:
var str = new StringBuilder();
str.Append(StreetAddress)
.Append(RuralRoute)
...
return str.ToString().GetHashCode();
Каковы преимущества / недостатки этого?Почему я не должен этого делать?
Решение
Я бы не стал этого делать просто на том основании, что это бессмысленно создает кучу строк - хотя замечание Kosi2801 о том, что коллизии упрощаются, также актуально.(Я подозреваю, что этого бы не произошло на самом деле создает много столкновений, из-за природы полей, но ...)
Я бы выбрал "простой и легкодоступный алгоритм", который я описывал ранее используется в этом ответе (спасибо, что посмотрели это, лэнс :) - и который указан в Effective Java, как вы сказали.В этом случае это закончилось бы следующим образом:
public int GetHashCode()
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + StreetAddress.GetHashCode();
hash = hash * 23 + RuralRoute.GetHashCode();
hash = hash * 23 + City.GetHashCode();
hash = hash * 23 + Province.GetHashCode();
hash = hash * 23 + Country.GetHashCode();
hash = hash * 23 + PostalCode.GetHashCode();
return hash;
}
Конечно, это небезопасно для null.Если вы используете C # 3, возможно, вам захочется рассмотреть метод расширения:
public static int GetNullSafeHashCode<T>(this T value) where T : class
{
return value == null ? 1 : value.GetHashCode();
}
Тогда вы можете использовать:
public int GetHashCode()
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + StreetAddress.GetNullSafeHashCode();
hash = hash * 23 + RuralRoute.GetNullSafeHashCode();
hash = hash * 23 + City.GetNullSafeHashCode();
hash = hash * 23 + Province.GetNullSafeHashCode();
hash = hash * 23 + Country.GetNullSafeHashCode();
hash = hash * 23 + PostalCode.GetNullSafeHashCode();
return hash;
}
Ты мог бы создайте утилиту метода массива параметров, чтобы сделать это еще проще:
public static int GetHashCode(params object[] values)
{
int hash = 17;
foreach (object value in values)
{
hash = hash * 23 + value.GetNullSafeHashCode();
}
return hash;
}
и назовите это с помощью:
public int GetHashCode()
{
return HashHelpers.GetHashCode(StreetAddress, RuralRoute, City,
Province, Country, PostalCode);
}
В большинстве типов задействованы примитивы, так что выполнение боксирования было бы несколько излишним, но в этом случае у вас были бы только ссылки.Конечно, в конечном итоге вы создадите массив без необходимости, но вы знаете, что говорят о преждевременной оптимизации...
Другие советы
Не делайте этого, потому что объекты могут быть разными, хотя хэш-код один и тот же.
Подумайте о
"StreetAddress" + "RuralRoute" + "City"
против
"Street" + "AddressRural" + "RouteCity"
Оба будут иметь один и тот же хэш-код, но разное содержимое в полях.
Для такого рода вещей вы, возможно, захотите реализовать IEqualityComparer<Address>
:
public class Address : IEqualityComparer<Address>
{
//
// member declarations
//
bool IEqualityComparer<Address>.Equals(Address x, Address y)
{
// implementation here
}
int IEqualityComparer<Address>.GetHashCode(Item obj)
{
// implementation here
}
}
Вы также могли бы реализовать IComparable<Address>
чтобы получить заказ...
public string getfourDigitEncryptedText(string input) {
int hashCode = input.hashCode();
string hstring = (new StringBuilder()).append(hashCode).append("").toString();
string rev_hstring = (new StringBuilder(hstring)).reverse().toString();
string parts[] = rev_hstring.trim().split("");
int prefixint = 0;
for(int i = 1; i <= parts.length - 3; i++)
prefixint += integer.parseInt(parts[i]);
string prefixstr = "0";
if((new integer(prefixint)).toString().length() < 2)
prefixstr = (new StringBuilder()).append((new integer(prefixint)).toString()).append("5").toString();
else if((new integer(prefixint)).toString().length() > 2)
prefixstr = "79";
else
prefixstr = (new integer(prefixint)).toString();
string finalstr = (new StringBuilder()).append(prefixint).append(rev_hstring.substring(3, 5)).toString();
return finalstr;
}