为什么KeyValuePair没有重写Equals()和GetHashCode()?

10
我打算在一个比较密集的代码中使用“KeyValuePair”,但是查看它在.NET中的实现方式时感到困惑(见下文)。为什么它不为了效率而覆盖“Equals”和“GetHashCode”(并且不实现“==”),反而使用基于缓慢反射的默认实现呢?我知道结构体/值类型有一个基于反射的默认实现,用于它们的“GetHashCode()”和“Equals(object)”方法,但是如果要进行大量比较的话,我认为与覆盖相等性相比,它非常低效。编辑:我做了一些测试,并发现在我的情况下(WPF列表),“KeyValuePair”默认实现和我自己实现的覆盖“GetHashCode()”和“Equals(object)”的结构体都比作为类实现更慢!http://referencesource.microsoft.com/#mscorlib/system/collections/generic/keyvaluepair.cs,8585965bb176a426
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Interface:  KeyValuePair
** 
** <OWNER>[....]</OWNER>
**
**
** Purpose: Generic key-value pair for dictionary enumerators.
**
** 
===========================================================*/
namespace System.Collections.Generic {

    using System;
    using System.Text;

    // A KeyValuePair holds a key and a value from a dictionary.
    // It is used by the IEnumerable<T> implementation for both IDictionary<TKey, TValue>
    // and IReadOnlyDictionary<TKey, TValue>.
    [Serializable]
    public struct KeyValuePair<TKey, TValue> {
        private TKey key;
        private TValue value;

        public KeyValuePair(TKey key, TValue value) {
            this.key = key;
            this.value = value;
        }

        public TKey Key {
            get { return key; }
        }

        public TValue Value {
            get { return value; }
        }

        public override string ToString() {
            StringBuilder s = StringBuilderCache.Acquire();
            s.Append('[');
            if( Key != null) {
                s.Append(Key.ToString());
            }
            s.Append(", ");
            if( Value != null) {
               s.Append(Value.ToString());
            }
            s.Append(']');
            return StringBuilderCache.GetStringAndRelease(s);
        }
    }
}

结构体有一个默认实现,基于反射实现其 GetHashCode()Equals(object) 方法。显然这不是高效的,但它确实在一定程度上减少了正确实现的需要。 - Jeroen Vannevel
你的代码能够编译通过是因为在==表达式中比较的是.GetHashCode()而不是KeyValuePair - Jeroen Vannevel
TKey类型必须已经有一个非常好的GetHashCode实现,否则字典将会非常糟糕。因此,CLR为结构体实现的默认GetHashCode已经足够好了。试图使Equals更好是没有意义的。 - Hans Passant
3个回答

20
作为其他答案所指出的,您可以免费获得相等性和哈希功能,因此您不需要覆盖它们。但是,您会得到您支付的代价;相等性和哈希的默认实现在某些情况下(1)并不特别有效,并且(2)可能进行位比较,因此可以将负零和正零双倍数视为不同,而逻辑上它们是相等的。
如果您预计您的结构体经常在需要相等性和哈希的上下文中使用,则应编写自定义实现,并遵循适当的规则和指南。

https://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/

因此,回答你的问题:为什么没有人针对特定类型这样做?可能是因为他们认为相比于改进基础类库的其他事情,这样做不是很值得花费时间。大多数人不会比较键值对的相等性,所以优化它可能不是一个高优先级的任务。
当然,这只是推测;如果你真的想知道为什么某件事在特定的一天没有完成,你需要追踪所有没有执行该操作的人,并问问他们在那一天做了什么更重要的事情。

每天我们都从Eric那里学到新东西。 new KeyValuePair<int, int>(-0, 1).Equals(new KeyValuePair<int, int>(0, 1)); // true - Zein Makki
6
@user3185569:那不是有趣的情况。有趣的情况是:struct S { public double d; public S(double d) { this.d = d; } } ... Console.WriteLine(new S(-0.0).Equals(new S(0.0)));,它打印出 False,尽管这两个双精度浮点数显然是相等的。 - Eric Lippert
9
同样令人恐惧的是,如果您的结构体使用了 decimal,那么对于像 1.0M1.00M 这样的调用 GetHashCode 的内容,虽然 .Equals 返回为真,但会得到不同的结果。http://www.volatileread.com/utilitylibrary/snippetcompiler?id=64818 - Brian
1
@Brian:确实,那很糟糕。 - Eric Lippert
1
对于 .Net Core,可能需要修复此问题的链接是 https://github.com/dotnet/coreclr/pull/13164 和 https://github.com/dotnet/coreclr/issues/16545。 - Brian

7

这是一个结构体,结构体继承自ValueType类型,而该类型已经重写了Equals和GetHashCode方法的实现

它不支持==运算符,以下操作甚至无法编译通过。

var result = new KeyValuePair<string, string>("KVP", "Test1") ==
         new KeyValuePair<string, string>("KVP", "Test2");

您会收到错误消息"运算符'=='无法应用于类型为KeyValuePair<string, string>KeyValuePair<string, string>的操作数"

3
== 没有重载,因此它不能编译。 - Servy
@Servy 谢谢,我试过了并添加了更详细的部分来处理 == 部分。 - Scott Chamberlain
我的问题是为什么他们不覆盖默认实现,因此抱歉,我要点踩。 - Mikhail Poda
@Mikhail 在下面回答,ValueType 抽象类有一个通用的 Equals 功能,可以比较结构体中的每个字段。 - Zein Makki
2
@Mikhail因为它不需要。.NET框架从不在KeyValuePair上调用.GetHashCode或.Equals。它会在TKey上调用它。框架从不使用它,因此没有理由覆盖它。 - Scott Chamberlain
@Scott:这仍然不是一个有效的理由,即框架从不使用KeyValuePair.GetHashCode()(另外,你确定吗?)。它必须为我们,框架的用户来实现。 - Mikhail Poda

-2

KeyValuePair 是一个结构体(隐式继承自 ValueType),并且相等性运算正常工作:

var a = new KeyValuePair<string, string>("a", "b");
var b = new KeyValuePair<string, string>("a", "b");

bool areEqual = a.Equals(b); // true

以下是等式策略的参考:
1- 相同的引用。
2- 可按位比较。
3- 使用反射比较结构中的每个字段。
 public abstract class ValueType {

        [System.Security.SecuritySafeCritical]
        public override bool Equals (Object obj) {
            BCLDebug.Perf(false, "ValueType::Equals is not fast.  "+this.GetType().FullName+" should override Equals(Object)");
            if (null==obj) {
                return false;
            }
            RuntimeType thisType = (RuntimeType)this.GetType();
            RuntimeType thatType = (RuntimeType)obj.GetType();

            if (thatType!=thisType) {
                return false;
            }

            Object thisObj = (Object)this;
            Object thisResult, thatResult;

            // if there are no GC references in this object we can avoid reflection 
            // and do a fast memcmp
            if (CanCompareBits(this))
                return FastEqualsCheck(thisObj, obj);

            FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            for (int i=0; i<thisFields.Length; i++) {
                thisResult = ((RtFieldInfo)thisFields[i]).UnsafeGetValue(thisObj);
                thatResult = ((RtFieldInfo)thisFields[i]).UnsafeGetValue(obj);

                if (thisResult == null) {
                    if (thatResult != null)
                        return false;
                }
                else
                if (!thisResult.Equals(thatResult)) {
                    return false;
                }
            }

            return true;
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接