为什么C#字典不调用隐藏的GetHashCode方法
我在一个项目中广泛使用了通用C#。我需要复合键,所以我一直使用as键。在某个时候,我想知道使用一个缓存哈希代码的自定义类是否有益:为什么C#字典不调用隐藏的GetHashCode方法,c#,dictionary,C#,Dictionary,我在一个项目中广泛使用了通用C#。我需要复合键,所以我一直使用as键。在某个时候,我想知道使用一个缓存哈希代码的自定义类是否有益: public class CompositeKey<T1, T2> : Tuple<T1, T2> { private readonly int _hashCode; public CompositeKey(T1 t1, T2 t2) : base(t1, t2) { _hashCode = base
public class CompositeKey<T1, T2> : Tuple<T1, T2>
{
private readonly int _hashCode;
public CompositeKey(T1 t1, T2 t2) : base(t1, t2)
{
_hashCode = base.GetHashCode();
}
public new int GetHashCode()
{
return _hashCode;
}
}
然而,我注意到我的自定义GetHashCode
方法根本没有被调用。当我将new
更改为override
时,它按预期被调用
有人能解释为什么不调用隐藏的GetHashCode
方法吗?如果我像这样定义字典,我会期待这种行为
var dict = new Dictionary<Tuple<string, int>, int>();
var dict=newdictionary();
但如果我像我的示例中那样显式地指定CompositeKey
类型,则不会
另外,我知道隐藏
GetHashCode
方法可能不是一个好主意。泛型类型中方法调用的重载解析发生在编译未绑定泛型类型(例如Dictionary
)时,而不是构造封闭类型(例如Dictionary
)时
由于Dictionary
中对TKey
没有约束,因此可用的GetHashCode()
的唯一重载是object.GetHashCode()
。构造一个具有更好重载的GetHashCode()
类型不会更改初始重载解析
它不仅限于使用new
隐藏的方法。重载方法也会发生同样的情况:
class Generic<T>
{
public bool Equal(T t1, T t2)
{
return t1.Equals(t2);
}
}
class X : IEquatable<X>
{
public override bool Equals(object obj)
{
Console.WriteLine("object.Equals");
return true;
}
public bool Equals(X other)
{
Console.WriteLine("IEquatable.Equals");
return true;
}
}
有人能解释为什么不调用隐藏的GetHashCode方法吗?
如果我将字典定义为
这个
为了能够调用CompositeKey.GetHashCode
方法,必须在编译时将CompositeKey
实例的引用键入为CompositeKey
但是字典
的代码库不知道您的复合键
类(显然)。它所知道的只是TKey
(泛型类型参数),它与没有任何约束的System.Object
一样等效。因为除了在System.Object
中声明的方法之外,您不能调用t
的任何方法,而无约束
因此,Dictionary最终调用了类中未重写的
Object.GetHashCode
,因此它没有被调用。这是因为泛型上的类型约束。下面是一个简化的程序来说明这个问题
public class Program
{
public static void Main(string[] args)
{
var bar = new Bar();
TestMethod(bar);
TestMethod2(bar);
}
public static void TestMethod<T>(T obj) where T : Foo
{
obj.Test();
obj.Test2();
}
public static void TestMethod2<T>(T obj) where T : Bar
{
obj.Test();
obj.Test2();
}
}
public class Foo
{
public virtual void Test()
{
Debugger.Break();
}
public virtual void Test2()
{
Debugger.Break();
}
}
public class Bar : Foo
{
public new void Test()
{
Debugger.Break();
}
public override void Test2()
{
Debugger.Break();
}
}
现在,关于您的问题,正在使用的比较器是
这就是为什么函数只在您重写它时才被调用,而在您隐藏它时才被调用。仅详细说明前面的答案。new的问题在于,只有当使用者直接操作该类(在本例中是您的CompositeKey类)时,它才会覆盖该方法。对CompositeKey派生自的任何基类的任何调用都不会调用您的新成员 因此,如果在以下情况下:
- 隐藏是个坏主意,因为它会导致这个问题!这里有一篇文章解释了“新建”和“覆盖”之间的区别。。。另一方面,如果你认为你的hashcode算法需要缓存,那可能是一个糟糕的算法,或者你缓存了不必要的东西。没错,这肯定是一个微观优化,但我仍然好奇它是否会产生可测量的差异。在我的测试用例中,
甚至比CompositeKey
的性能稍差(当使用元组
和缓存的哈希代码时)。@AndyJ我知道隐藏通常是个坏主意,在这种情况下我永远不会使用它。但是假设有人向我展示了我的例子,并问我在这种特定情况下使用覆盖
而不是new
是否会有所不同,我会说不。但我想你每天都会学到一些新东西:)谢谢你的例子。我不知道这些约束除了在您试图构造泛型类型的新实例时进行类型检查之外,还有其他任何影响。override
var test = new Generic<X>(); test.Equal(new X(), new X()); // prints "object.Equals"
public class Program { public static void Main(string[] args) { var bar = new Bar(); TestMethod(bar); TestMethod2(bar); } public static void TestMethod<T>(T obj) where T : Foo { obj.Test(); obj.Test2(); } public static void TestMethod2<T>(T obj) where T : Bar { obj.Test(); obj.Test2(); } } public class Foo { public virtual void Test() { Debugger.Break(); } public virtual void Test2() { Debugger.Break(); } } public class Bar : Foo { public new void Test() { Debugger.Break(); } public override void Test2() { Debugger.Break(); } }
public static void TestMethod<T>(T obj) { ((Foo)obj).Test(); //You would expect this to call Foo.Test() b\c of shadowing ((Foo)obj).Test2(); //You would expect this to call Bar.Test2() b\c of overloading }
[Serializable] internal class ObjectEqualityComparer<T>: EqualityComparer<T> { [Pure] public override bool Equals(T x, T y) { if (x != null) { if (y != null) return x.Equals(y); return false; } if (y != null) return false; return true; } [Pure] public override int GetHashCode(T obj) { if (obj == null) return 0; return obj.GetHashCode(); } //... }
public override bool Equals(T x, T y) { if (x != null) { if (y != null) return ((object)x).Equals(y); return false; } if (y != null) return false; return true; } [Pure] public override int GetHashCode(T obj) { if (obj == null) return 0; return ((object)obj).GetHashCode(); }