为IRevertibleChangeTracking类实现GetHashCode

时间:2018-10-08 03:42:01

标签: c# gethashcode

我当前的项目使用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);
}

Overrideing Equals然后在Visual Studio中生成一个警告,也将覆盖GetHashCode。根据我自己的研究(MSDN,Google,类似的StackOverflow问题),我开始了解到这一点:

  • 如果您覆盖等于,则还应该覆盖GetHashCode
  • 如果一个对象等于另一个对象,则GetHashCode应该为两个对象提供相同的值
  • 各种内部集合使用GetHashCode,并且在将我的类与这些集合一起使用时,未能正确覆盖它可能会导致问题。
  • GetHashCode中的值在对象的生命周期内不应更改

由于我的类的属性全部都可以更改(即使ID设置为-1并为新对象更新),而不是依赖于当前值,我可以使用ID属性和OriginalValues字典实现GetHashCode,因为它们更改最少:

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(因此为GetHashCode()产生不同的值)后才有效。

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

public override int GetHashCode()
{
    return 0;
}

无论我的类的属性被更改了多少次,返回的值始终是相同的。

问题

  1. return 0GetHashCode的有效实现,用于预期其属性会发生变化的可变对象吗?
  2. 此实施是否需要注意一些弊端? (字典查找的性能较差吗?)

2 个答案:

答案 0 :(得分:0)

如果仅需要Equals进行测试,则不要修改您的课程。

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

firstAsset.Should().BeEquivalentTo(secondAsset);

让您在课堂上的代码保持整洁。

答案 1 :(得分:0)

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

这样做意味着类的ID属性在创建后就不会更改,这使我可以使用以下方式创建唯一的哈希码

public override int GetHashCode()
{
    return this.Id.GetHashCode();
}