Реализуйте GetHashCode для класса IRevertibleChangeTracking - PullRequest
0 голосов
/ 08 октября 2018

Мой текущий проект использует Dapper для создания класса на основе строк из базы данных.

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

public class Computer
{
    public int Id { get; internal set; }
    public string OperatingSystem { get; set; }
}

public class Hardware : IRevertibleChangeTracking
{
    public int Id { get; internal set; }

    private string serialNumber;
    public string SerialNumber
    {
        get => this.serialNumber;
        set => this.SetField(ref this.serialNumber, value);
    }

    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        // compare against OriginalValues and store changed value in Changes
    }

    public ObservableCollection<Computer> Computers { get; }

    private Dictionary<string, object> OriginalValues { get; set; }

    // Equals override
    public override bool Equals(object value)
    {
        return this.Equals(value as Hardware);
    }

    // Equals implementation
    public bool Equals(Hardware hardware)
    {
        if (ReferenceEquals(null, hardware)) return false;
        if (ReferenceEquals(this, hardware)) return true;

        return object.Equals(this.Id, hardware.Id)
           && string.Equals(this.SerialNumber, hardware.SerialNumber)
           && this.Computers.SequenceEquals(hardware.Computers);

    public static bool operator ==(Hardware hardwareA, Hardware hardwareB)
    {
        if (object.ReferenceEquals(hardwareA, hardwareB))
        {
            return true;
        }

        return !object.ReferenceEquals(null, hardwareA) && hardwareA.Equals(hardwareB);
    }       

    public static bool operator !=(Hardware hardwareA, Hardware hardwareB)
    {
        return !(hardwareA == hardwareB);
    }

    public override void AcceptChanges()
    {
        // clear changes and update original calues
    }

    public override void RejectChanges()
    {
        // clear changes and revert object state
    }
}

Я переопределил Equals, чтобы позволить моему классу сравнивать себя с другими экземплярами и упростить модульные тесты, включающие все свойства:

[Test]
public void CanCheckTwoHardwareAreTheSameWithEquals()
{
    var firstHardware = Database.GetHardwareById(931);
    var secondHardware = Database.GetHardwareById(931);

    // i would rather this than asserting 50 properties are equal
    Assert.IsTrue(firstAsset == secondAsset);
}

Тогда переопределяя Equalsгенерирует предупреждение в Visual Studio, чтобы также переопределить GetHashCode.Основываясь на моих собственных исследованиях ( MSDN , поиск в Google, похожие вопросы StackOverflow), я пришел к выводу, что:

  • Если вы переопределяете Equals, вам также следует переопределить GetHashCode
  • Если объект равен другому объекту, GetHashCode должен предоставлять одинаковое значение для обоих объектов
  • GetHashCode используется различными внутренними коллекциями, и неправильная его переопределение может вызвать проблемы при использовании моего класса с этими коллекциями
  • Значение из GetHashCode не должно изменяться в течение всего времени жизни объекта

Поскольку все свойства моего класса могут изменяться (даже идентификатор установлен на -1 и обновлен дляновые объекты), вместо того, чтобы полагаться на текущие значения, я мог бы реализовать GetHashCode, используя свойство ID и словарь OriginalValues, поскольку они меняют наименьшее значение:

public override int GetHashCode()
{
    unchecked
    {
        // Choose large primes to avoid hashing collisions
        const int HashBase = (int)2166136261;
        const int HashMultiplier = 16777619;

        var hash = HashBase;
        hash = (hash * HashMultiplier) ^ (!object.ReferenceEquals(null, this.Id) ? this.Id.GetHashCode() : 0);
        hash = (hash * HashMultiplier) ^ (!object.ReferenceEquals(null, this.OriginalValues["SerialNumber"]) ? this.OriginalValues["SerialNumber"].GetHashCode() : 0);
        return hash;
    }
}

Однако это будет работать, пока кто-то не вызовет AcceptChanges()метод в моем классе и OriginalValues ​​изменены (таким образом, производяt значение для GetHashCode()).

Я также видел несколько ответов, которые просто return 0 как реализация GetHashCode()

public override int GetHashCode()
{
    return 0;
}

. Это вернет значение, котороевсегда одно и то же, независимо от того, сколько раз менялись свойства моего класса.

Вопросы

  1. Является ли return 0 допустимой реализацией GetHashCode для изменчивых объектов, свойства которых, как ожидается, изменятся?
  2. Есть ли какие-либо недостатки, о которых следует знать в этой реализации?(меньше производительности при поиске в словаре?)

Ответы [ 2 ]

0 голосов
/ 09 октября 2018

В итоге я переместил код создания из моего класса в класс фабрики, поскольку фабрика уже отвечает за создание экземпляров моего класса на основе уже существующих строк.

Это означает, что свойство Id на моемкласс никогда не изменяется после создания, это позволяет мне создать уникальный хэш-код с

public override int GetHashCode()
{
    return this.Id.GetHashCode();
}
0 голосов
/ 08 октября 2018

Если вам нужно Equals только для тестирования, не изменяйте свой класс.

Я предпочитаю использовать метод FluentAssertions.BeEquivalentTo.Он сравнивает объекты по свойствам.

firstAsset.Should().BeEquivalentTo(secondAsset);

Сом, ваш код в классе останется чистым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...