为 IRevertibleChangeTracking 实施 GetHashCode class

Implement GetHashCode for IRevertibleChangeTracking class

我当前的项目使用 Dapper 基于数据库中的行构建 class。

我已经缩减了我的 class 以专注于我问题的平等部分,所以如果需要任何额外的代码,请告诉我:

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 以允许我的 class 将自身与其他实例进行比较并简化涉及所有属性的单元测试:

[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);
}

Overriding Equals 然后在 Visual Studio 中生成了一个警告也覆盖了 GetHashCode。根据我自己的研究(MSDN、谷歌搜索、类似的 Whosebug 问题)我了解到:

由于我的 class 的属性都可以更改(甚至 ID 设置为 -1 并为新对象更新),而不是依赖于当前值,我可以使用 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;
    }
}

然而,只有当有人在我的 class 上调用 AcceptChanges() 方法并且 OriginalValues 被更改(从而为 GetHashCode() 产生不同的值)时,这才会起作用。

我在这里也看到了一些答案,它们只是 return 0 作为 GetHashCode()

的实现
public override int GetHashCode()
{
    return 0;
}

这将 return 一个始终相同的值,无论我 class 上的属性更改多少次。

问题

  1. return 0 是否是 GetHashCode 的有效实现,适用于属性预计会更改的可变对象?
  2. 此实施是否有任何需要注意的缺点? (字典查找性能下降?)

如果您只需要 Equals 进行测试,请不要修改您的 class。

我更喜欢使用FluentAssertions.BeEquivalentTo方法。它按属性比较对象。

firstAsset.Should().BeEquivalentTo(secondAsset);

所以您在 class 中的代码将保持干净。

我最终将创建代码从我的 class 移到了工厂 class,因为工厂已经负责根据已经存在的行创建我的 class 的实例。

这样做意味着我的 class 上的 ID 属性 在创建后永远不会改变,这使我可以使用

创建一个唯一的哈希码
public override int GetHashCode()
{
    return this.Id.GetHashCode();
}