【问题标题】:ReSharper Equality member generation Hashcode warningReSharper Equality 成员生成哈希码警告
【发布时间】:2017-04-12 10:35:34
【问题描述】:

自 R# 2017.1.1 起,我在自动生成的 GetHashCode() 函数中收到警告。让我解释一下这个函数是如何创建的:

如果您覆盖类的Equals 函数,R# 建议您创建相等成员。如果您让 R# 生成这些相等成员,它也会覆盖 GetHashCode() 函数。然而,它在那里使用了我班级的所有属性。由于这些属性并非都是只读的,R# 告诉我应该将它们设为只读并显示警告。

所以我的问题是我是否应该将 GetHashCode() 函数留空(或删除带有警告的那些部分),或者我是否应该尝试将属性设置为只读以便 R# 不再警告我。

这是我的代码:

public override bool Equals(object obj) => obj is RamEntry && Equals((RamEntry) obj);

public override int GetHashCode()
{
    unchecked
    {
        var hashCode = (Value != null ? Value.GetHashCode() : 0);   //In those 5 lines the warnings are being displayed
        hashCode = (hashCode * 397) ^ Unimportant.GetHashCode();    //-------------------------------------------------
        hashCode = (hashCode * 397) ^ (Comment?.GetHashCode() ?? 0);//-------------------------------------------------
        hashCode = (hashCode * 397) ^ (Address?.GetHashCode() ?? 0);//-------------------------------------------------
        hashCode = (hashCode * 397) ^ LastUpdated.GetHashCode();    //-------------------------------------------------
        return hashCode;
    }
}

private bool Equals(RamEntry other)
    => string.Equals(Value, other.Value) && Unimportant == other.Unimportant &&
       string.Equals(Comment, other.Comment) && string.Equals(Address, other.Address);

如果我删除 GetHashCode() 函数,这是 R# 给我的警告:

这是GetHashCode() 函数给我的警告:

【问题讨论】:

    标签: c# visual-studio refactoring resharper equals


    【解决方案1】:

    我认为最好通过示例解释为什么在覆盖相等成员时应该覆盖GetHashCode,以及为什么在计算哈希码时不应该使用可变字段\属性。考虑这个简单的类:

    public class Test {
        public string First { get; set; }
        public string Second { get; set; }
    }
    

    现在,如果两个 Test 对象的 FirstSecond 属性相等,则您确定它们相等。您生成相等成员但不覆盖GetHashCode

    public class Test {
        public string First { get; set; }
        public string Second { get; set; }
        public override bool Equals(object obj) {
            return obj is Test && Equals((Test)obj);
        }
    
        protected bool Equals(Test other) {
            return string.Equals(First, other.First) && string.Equals(Second, other.Second);
        }
    
        public static bool operator ==(Test left, Test right) {
            return Equals(left, right);
        }
    
        public static bool operator !=(Test left, Test right) {
            return !Equals(left, right);
        }
    }
    

    然后你在Hashset之类的东西中使用你的Test对象。

    var test1 = new Test();
    test1.First = "a";
    test1.Second = "b";
    var set = new HashSet<Test>();
    set.Add(test1);
    var test2 = new Test();
    test2.First = "a";
    test2.Second = "b";
    bool equal = test1 == test2; // true
    bool contains = set.Contains(test2); // nope, false
    

    出了点问题 - set 显然包含一个等于 test2 的项目,但是 我们没能找到它,因为我们没有覆盖GetHashCode(这是最近的一个例子,说明当它被违反时事情是如何严重错误的:https://stackoverflow.com/a/43356217/5311735)。所以我们实现它:

    public override int GetHashCode() {
        unchecked {
            return ((First != null ? First.GetHashCode() : 0) * 397) ^ (Second != null ? Second.GetHashCode() : 0);
        }
    }
    

    现在我们的问题已解决,set.Contains 返回 true。到目前为止一切顺利,但我们使用可变属性来计算哈希码。所以考虑一下:

    var test1 = new Test();
    test1.First = "a";
    test1.Second = "b";
    var set = new HashSet<Test>();
    set.Add(test1);
    test1.First = "c";
    var contains = set.Contains(test1); // nope, false
    

    所以我们将项目添加到哈希集中,修改了一个属性,现在 set.Contains 为我们之前添加几行的完全相同的项目返回 false。也就是说,从我们将项目放入集合的那一刻到我们调用Contains 的那一刻,哈希码已经发生了变化。

    如果您不想要这样的意外 - 当您覆盖相等成员并且不要在那里使用可变字段\属性时,请始终覆盖 GetHashCode

    【讨论】:

    • 很好的解释,但在他的情况下,恕我直言,他对Equals() 的实现与基类的功能没有决定性的不同,除了你的例子。那么您是否同意,OP 不一定会实现 GetHashCode()
    • @LuckyLikey Equals 的基本实现将只比较对象实例是否相同(即 - 指向相同的内存位置)。这不是这里的情况 - 如果这些属性(如 CommentAddress 等)相等,则认为两个 RamEntry 相等。如果它们是原始类型(int、string 等)或者它们是结构,或者它们是也正确实现 Equals 的类 - 这与基本实现不同。所以他的情况与我在回答中描述的基本相同。如果 RamEntry1 == RamEntry2 那么它们的哈希码也应该相等。
    • @Evk 那么如果没有不可变属性,在这种情况下如何实现GetHashCode
    • @Konrad 好吧,最好不要这样做。如果无法更改设计并且您绝对必须将具有可变属性的对象存储在 hashset 或类似文件中 - 您必须准备好面对本答案中描述的细微错误。在可变属性的情况下,没有“好”的方法来实现 GetHashCode。
    • @Konrad 如果您必须将此类对象存储在 HashSet 中 - 像往常一样使用可变属性实现 GetHashCode。但是,在将对象放入 HashSet 之后,必须注意不要再修改属性。您可以为此类对象实现“可冻结”功能 - 将其放入 HashSet 后,以尝试修改任何属性都会引发异常的方式“冻结”该对象。
    【解决方案2】:

    您应该删除整个GetHashCode() Stuff,或者如果您需要它的功能,请正确实施它。

    如果您只想测试是否相等,则不需要它 (Source):

    不要测试哈希码的相等性以确定两个对象是否相等。 (不相等的对象可以有相同的哈希码。)要测试相等,调用 ReferenceEquals 或 Equals 方法

    如果您想何时实施请考虑link,它还包含基本实施指南。

    GetHashCode() 中的只读成员为什么不发出警告

    通常GetHashCode() 用于 - 哦,真是个奇迹 - 在哈希表或字典中。这就是为什么你应该确保,只要对象在一个集合和可能依赖该值的东西中,哈希码至少不会改变。这就是您收到Warning on not readonly members 的原因。

    您可以为不可变引用类型覆盖 GetHashCode。通常,对于可变引用类型,您应该仅在以下情况下覆盖 GetHashCode:
    – 您可以从不可变的字段中计算哈希码;或者 – 当对象包含在依赖于其哈希码的集合中时,您可以确保可变对象的哈希码不会改变。

    为什么不覆盖GetHashCode() 会给出警告?

    我的意思是这很容易,想象一下如果你真的实现了一个全新的策略当一个对象等于另一个对象,你可能有一个特殊的哈希码创建。

    编辑:

    实际上,Equals 方法中的代码部分确实需要实现GetHashCode()。因为平等现在不再依赖于堆上的对象实例。正如@Evk 所指出的,这显然是一个比较对象的全新策略

    【讨论】:

    • 其实我不会自己实现它,因为我看不到它对我有什么用,但是如果我删除它,R#会给我一个警告('TRDebugger.Types.RamEntry ' 覆盖 Object.Equals(object o) 但不覆盖 Object.GetHashCode())。我会更新我的问题以明确这一点
    • @MetaColon 我更新了我的答案以更适合您关于这些警告的问题
    猜你喜欢
    • 2010-11-07
    • 2013-04-11
    • 1970-01-01
    • 2016-02-06
    • 1970-01-01
    • 1970-01-01
    • 2017-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多