Вопрос

У меня есть неизменяемый объект значения IPathwayModule, значение которого определяется:

  • (int) Блокировать;
  • (Entity) Модуль, идентифицируемый (строкой) ModuleId;
  • (перечисление) Статус;и
  • (сущность) Класс, идентифицируемый (строкой) ClassId, который может быть нулевым.

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

private class StandardPathwayModuleComparer : IEqualityComparer<IPathwayModule>
{
    public bool Equals(IPathwayModule x, IPathwayModule y)
    {
        int hx = GetHashCode(x);
        int hy = GetHashCode(y);
        return hx == hy;
    }

    public int GetHashCode(IPathwayModule obj)
    {
        int h;
        if (obj.Class != null)
        {
            h = obj.Block.GetHashCode() + obj.Module.ModuleId.GetHashCode() + obj.Status.GetHashCode() + obj.Class.ClassId.GetHashCode();
        }
        else
        {
            h = obj.Block.GetHashCode() + obj.Module.ModuleId.GetHashCode() + obj.Status.GetHashCode() + "NOCLASS".GetHashCode();
        }
        return h;
    }
}

IPathwayModule определенно является неизменяемым, и разные экземпляры с одинаковыми значениями должны быть равны и создавать один и тот же HashCode, поскольку они используются как элементы в HashSets.

Полагаю, мои вопросы:

  • Правильно ли я использую интерфейс в этом случае?
  • Есть ли случаи, когда я могу не увидеть желаемого поведения?
  • Есть ли способ улучшить надежность и производительность?
  • Есть ли какие-то передовые практики, которым я не следую?
Это было полезно?

Решение 7

Спасибо всем, кто ответил.Я собрал отзывы всех, кто ответил, и улучшил свои результаты. IEqualityComparer теперь выглядит так:

private class StandardPathwayModuleComparer : IEqualityComparer<IPathwayModule>
{
    public bool Equals(IPathwayModule x, IPathwayModule y)
    {
        if (x == y) return true;
        if (x == null || y == null) return false;

        if ((x.Class == null) ^ (y.Class == null)) return false;

        if (x.Class == null) //and implicitly y.Class == null
        {
            return x.Block.Equals(y.Block) && x.Status.Equals(y.Status) && x.Module.ModuleId.Equals(y.Module.ModuleId);
        }
        return x.Block.Equals(y.Block) && x.Status.Equals(y.Status) && x.Module.ModuleId.Equals(y.Module.ModuleId) && x.Class.ClassId.Equals(y.Class.ClassId);
    }
    public int GetHashCode(IPathwayModule obj)
    {
        unchecked {
            int h = obj.Block ^ obj.Module.ModuleId.GetHashCode() ^ (int) obj.Status;
            if (obj.Class != null)
            {
               h ^= obj.Class.ClassId.GetHashCode();
            }
            return h;
        }
    }
}

Другие советы

Не делайте Equals с точки зрения результатов хэш-функции, это слишком хрупко.Вместо этого выполните сравнение значений полей для каждого из полей.Что-то вроде:

return x != null && y != null && x.Name.Equals(y.Name) && x.Type.Equals(y.Type) ...

Кроме того, результаты хэш-функций на самом деле не поддаются сложению.Попробуйте использовать ^ вместо этого оператор.

return obj.Name.GetHashCode() ^ obj.Type.GetHashCode() ...

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

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

GetHashCode() можно немного улучшить.Вам не нужно вызывать GetHashCode() для целого числа.Int сам по себе является хорошим хеш-кодом.То же самое для значений перечисления.Затем ваш GetHashCode можно реализовать следующим образом:

public int GetHashCode(IPathwayModule obj)
{
    unchecked {
        int h = obj.Block + obj.Module.ModeleId.GetHashCode() + (int) obj.Status;
        if (obj.class != null)
           h += obj.Class.ClassId.GetHashCode();
        return h;
    }
}

Блок «непроверенный» необходим, поскольку при арифметических операциях могут возникнуть переполнения.

Не следует использовать GetHashCode() в качестве основного способа сравнения объектов.Сравните это по полю.

Может существовать несколько объектов с одним и тем же хеш-кодом (это называется «коллизиями хеш-кода»).

Кроме того, будьте осторожны при сложении нескольких целочисленных значений, поскольку вы можете легко вызвать исключение OverflowException.Используйте «эксклюзивное или» (^), чтобы объединить хэш-коды или обернуть код в «непроверенный» блок.

Вам следует реализовать лучшие версии Equals и GetHashCode.

Например, хэш-код перечислений — это просто их числовое значение.

Другими словами, с этими двумя перечислениями:

public enum A { x, y, z }
public enum B { k, l, m }

Затем с вашей реализацией следующий тип значения:

public struct AB {
    public A;
    public B;
}

следующие два значения будут считаться равными:

AB ab1 = new AB { A = A.x, B = B.m };
AB ab2 = new AB { A = A.z, B = B.k };

Я предполагаю, что ты этого не хочешь.

Кроме того, передача типов значений в качестве интерфейсов приведет к их упаковке, что может привести к проблемам с производительностью, хотя, вероятно, не так уж и сильно.Вы можете подумать о том, чтобы реализация IEqualityComparer напрямую принимала ваши типы значений.

  1. Предполагать, что два объекта равны, потому что их хэш-код равен, неверно.Вам нужно сравнить всех участников индивидуально
  2. Вероятно, лучше использовать ^, а не + для объединения хеш-кодов.

Если я вас хорошо понимаю, вы хотели бы услышать некоторые комментарии по вашему коду.Вот мои замечания:

  1. GetHashCode должны быть объединены с помощью XOR, а не добавлены.исключающее ИЛИ (^) дает больше шансов предотвратить столкновения
  2. Вы сравниваете хеш-коды.Это хорошо, но делайте это только в том случае, если базовый объект переопределяет GetHashCode.Если нет, используйте свойства и их хэш-коды и объедините их.
  3. Хэш-коды важны, они делают возможным быстрое сравнение.Но если хэш-коды равны, объект все равно может быть другим.Это бывает редко.Но вам нужно будет сравнить поля вашего объекта, если хеш-коды равны.
  4. Вы говорите, что ваши типы значений неизменяемы, но вы ссылаетесь на объекты (.Class), которые не являются неизменяемыми
  5. Всегда оптимизируйте сравнение, добавляя эталонное сравнение в качестве первого теста.Ссылки неравны, объекты неравны, значит, структуры неравны.

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

РЕДАКТИРОВАТЬ: вы сравниваете много строк.Сравнение строк оптимизировано в C#.Вы можете, как предлагали другие, лучше использовать == с ними в вашем сравнении.Для GetHashCode используйте ИЛИ ^ как предлагали и другие.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top