【问题标题】:Why do 2 delegate instances return the same hashcode?为什么 2 个委托实例返回相同的哈希码?
【发布时间】:2011-07-08 12:03:14
【问题描述】:

采取以下措施:

  var x =  new Action(() => { Console.Write("") ; });
  var y = new Action(() => { });
  var a = x.GetHashCode();
  var b = y.GetHashCode();
  Console.WriteLine(a == b);
  Console.WriteLine(x == y);

这将打印:

True
False

为什么哈希码相同?

这有点令人惊讶,并且会使在 Dictionary 中使用代表的速度与 List 一样慢(又名 O(n) 用于查找)。

更新:

问题是为什么。 IOW 谁做出了这样一个(愚蠢的)决定?

更好的哈希码实现应该是:

return Method ^ Target == null ? 0 : Target.GetHashcode();
// where Method is IntPtr

【问题讨论】:

  • 您是否也尝试将一些代码放入第二个委托,并且仅在检查哈希码之后?
  • 不知道为什么会这样,但作为一个想法,您可以实现自己的 Hash 机制,或者将这些操作包装在一个类中并覆盖其 GetHashCode 以满足您的需求。
  • 仅供参考,Delegate.Equals 的约定是:“判断指定对象和当前委托是否属于同一类型,是否共享相同的目标、方法和调用列表。”
  • @leppie 虽然我必须承认我从来没有遇到过我想在字典中使用委托作为 key 的场景。作为价值,肯定 - 只是不是关键。
  • 您的“更好的实现”与我最初犯的错误相同:您正在调用覆盖的 Target.GetHashCode 而不是使用引用相等。

标签: c# .net delegates hashcode


【解决方案1】:

简单!由于这里是GetHashCode的实现(坐在基类Delegate上):

public override int GetHashCode()
{
    return base.GetType().GetHashCode();
}

(坐在基类MulticastDelegate 将在上面调用):

public sealed override int GetHashCode()
{
    if (this.IsUnmanagedFunctionPtr())
    {
        return ValueType.GetHashCodeOfPtr(base._methodPtr);
    }
    object[] objArray = this._invocationList as object[];
    if (objArray == null)
    {
        return base.GetHashCode();
    }
    int num = 0;
    for (int i = 0; i < ((int) this._invocationCount); i++)
    {
        num = (num * 0x21) + objArray[i].GetHashCode();
    }
    return num;
}

使用Reflector等工具,我们可以看到代码,看起来默认实现就像我们上面看到的一样奇怪

这里的类型值将是Action。因此上面的结果是正确的

更新

【讨论】:

  • 老实说,return 42;Delegate 实现一样大约有用(尽管 MulticastDelegate 看起来更好)
  • 但是,GetHashCode 会在 MulticastDelegate 上依次被覆盖,这会将所有订阅委托的哈希码相加(如果它实际上是多播委托)。剧情变厚了……
  • @Marc: Object.GetType() 在运行时动态查找实际对象类型,因此它与typeof(object).GetHashCode() 不同。它实际上为每个委托类型返回不同的值(但对该类型的所有实例返回相同的值)
  • @Marc:我刚刚删除了我的评论; MulticastDelegate 实现同样被破坏,因为组合哈希码将通过调用每个目标的狡猾的基本实现来生成。如果你有不同的数量个目标,你会得到不同的哈希码,但如果你有相同数量的目标,那么你会得到相同的哈希码!
  • @thecoop:在非事件情况下,您多久为一个委托订阅多个方法?我大概可以用半手数数:)
【解决方案2】:

我第一次尝试更好的实现:

public class DelegateEqualityComparer:IEqualityComparer<Delegate>
{
    public bool Equals(Delegate del1,Delegate del2)
    {
        return (del1 != null) && del1.Equals(del2);
    }

    public int GetHashCode(Delegate obj)
    {
            if(obj==null)
                return 0;
            int result = obj.Method.GetHashCode() ^ obj.GetType().GetHashCode();
            if(obj.Target != null)
                result ^= RuntimeHelpers.GetHashCode(obj);
            return result;
    }
}

这样的质量对于单播委托应该是好的,但对于多播委托来说不是很好(如果我没记错的话,目标/方法返回最后一个元素委托的值)。

但我不确定它是否在所有极端情况下都能履行合同。

嗯,看起来质量需要目标的引用平等。

【讨论】:

  • @adrianm:你的评论没有意义。
  • 嗯,有趣的想法@adrianm。我认为 equals 的规范可能意味着引用相等而不是 .Equals()
  • 修改它以使用目标的引用相等。
  • @leppie:如果目标中的某些内容发生更改,则委托的哈希码也会更改,即您违反了字典键的约定。我无法准确指出原因,但我认为奇怪的哈希码实现与 equals 定义和不可变调用列表有关。
  • @adrianm:我明白你的意思,但永远不应该使用这样的键!
【解决方案3】:

这闻起来像这个线程中提到的一些案例,也许它会给你一些关于这种行为的指示。否则,您可以将其记录在那里:-)

What's the strangest corner case you've seen in C# or .NET?

Rgds GJ

【讨论】:

  • 具体是哪个帖子?如果这就是你的意思,它与问题中的那个无关
【解决方案4】:

来自 MSDN:

默认实现 GetHashCode 不保证 独特性或一致性;所以, 不得将其用作唯一对象 用于散列目的的标识符。 派生类必须覆盖 带有实现的 GetHashCode 返回一个唯一的哈希码。为了 最好的结果,哈希码必须是 基于实例的值 字段或属性,而不是静态的 字段或属性。

因此,如果您没有覆盖 GetHashCode 方法,它可能会返回相同的结果。我怀疑这是因为它是从定义而不是实例生成的。

【讨论】:

  • 合同允许这样做是显而易见的。问题是为什么实施如此糟糕。
  • 我的意思是 MS 通过说您需要覆盖默认实现以获取唯一值来逃避这一点。这意味着默认实现是无用的。正如 Aliostad 在上面指出的,我想说的是,默认值基于类型,而不是实例。大概是因为MS讨厌我们吧。
  • 现在如果我们能覆盖 C# 中的哈希码就好了。我知道在 CLR(通过 IL)中可以构建自定义(并且非常慢)委托。
  • @leppie:如果您担心哈希表等的性能,那么您可以将“做正确的事情”的IEqualityComparer&lt;T&gt; 传递给您的Dictionary&lt;K,V&gt;/HashSet&lt;T&gt; 等构造函数,或者敲定自定义DelegateDictionary&lt;K,V&gt;/DelegateHashSet&lt;T&gt; 自动完成。
  • @leppie:诚然,这并不理想,而且您一开始确实不需要这样做,但这可能是一个足够的解决方法。
猜你喜欢
  • 2014-05-06
  • 2015-05-29
  • 2012-08-13
  • 2014-03-03
  • 1970-01-01
  • 1970-01-01
  • 2013-02-08
  • 2020-06-22
  • 1970-01-01
相关资源
最近更新 更多