GetHashCode() для строковых классов, зависящих от OrdinalIgnoreCase

public class Address{
    public string ContactName {get; private set;}
    public string Company {get; private set;}
    //...
    public string Zip {get; private set;}
}

Я хотел бы реализовать понятие distint-адресов, поэтому я переопределял Equals() для проверки равенства без учета регистра во всех полях (так как это адреса США, я использовал Ordinal вместо InvariantCulture для максимальной производительности)

public override bool Equals(Object obj){
    if (obj == null || this.GetType() != obj.GetType())
        return false;

    Address o = (Address)obj;

    return  
    (string.Compare(this.ContactName, o.ContactName, StringComparison.OrdinalIgnoreCase) == 0) &&
    (string.Compare(this.Company, o.Company, StringComparison.OrdinalIgnoreCase) == 0)
    // ...
    (string.Compare(this.Zip, o.Zip, StringComparison.OrdinalIgnoreCase) == 0)
}

Я хотел бы написать GetHashCode() так же, как и (игнорирование неэффективности конкатенации на данный момент):

public override int GetHashCode(){
    return (this.contactName + this.address1 + this.zip).ToLowerOrdinal().GetHashCode();
}

но этого не существует. Что я должен использовать вместо этого? Или я должен просто использовать InvariantCulture в моем методе Equals()?

(Я думаю .ToLowerInvariant().GetHashCode(), но я не уверен на 100%, что InvariantCulture не может решить, что идентичный символ (например, акцент) имеет другой смысл в другом контексте.)

Ответ 1

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

Из документации по StringComparer.OrdinalIgnoreCase(основное внимание):

http://msdn.microsoft.com/en-us/library/system.stringcomparer.ordinalignorecase.aspx

StringComparer, возвращаемый свойством OrdinalIgnoreCase, обрабатывает символы в строках для сравнения, как если бы они были преобразованы в верхний регистр, используя соглашения инвариантной культуры, а затем выполняет простое сравнение байтов, которое является независимым языка. Это наиболее удобно при сравнении строк, которые генерируются программно или при сравнении без учета регистра ресурсов, таких как пути и имена файлов.

Ответ 2

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

Нет необходимости создавать временные строки только для вычисления хеш-кодов. Для StringComparison.OrdinalIgnoreCase используйте StringComparer.OrdinalIgnoreCase.GetHashCode()

Затем вам нужно объединить несколько хеш-кодов в один. XOR должен быть в порядке (потому что маловероятно, что почтовый индекс одного человека является другим именем контакта). Однако пуристы могут не согласиться.

public override int GetHashCode()
{
    return StringComparer.OrdinalIgnoreCase.GetHashCode(ContactName) ^
        StringComparer.OrdinalIgnoreCase.GetHashCode(Company) ^
        // ...
        StringComparer.OrdinalIgnoreCase.GetHashCode(Zip);
}

Сказав все это, я бы поставил под вопрос, разумно ли использовать композитную структуру, такую ​​как Address, как ключ к словарю. Но принцип справедлив для строк типа идентичности.