Почему 2 экземпляра делегата возвращают один и тот же хэш-код? - PullRequest
31 голосов
/ 08 июля 2011

Возьмите следующее:

  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) для поиска).

Обновление:

Вопрос в том, почему. Кто же принял такое (глупое) решение?

Лучшая реализация хэш-кода была бы:

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

Ответы [ 4 ]

9 голосов
/ 08 июля 2011

Легко! Так как здесь реализация 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. Следовательно, приведенный выше результат правильный .

UPDATE

3 голосов
/ 08 июля 2011

Моя первая попытка лучшей реализации:

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;
    }
}

Качество этого должно быть хорошим для одиночных делегатов, но не настолько для многоадресных делегатов (если я правильно помню, Target / Method возвращает значенияпоследнего элемента делегата).

Но я не совсем уверен, выполняет ли он контракт во всех угловых случаях.

Хм, похоже, качество требует ссылочного равенства целей.

1 голос
/ 08 июля 2011

Из MSDN:

Реализация по умолчанию GetHashCode не гарантирует уникальность или согласованность;поэтому его нельзя использовать в качестве уникального идентификатора объекта для целей хеширования.Производные классы должны переопределять GetHashCode реализацией, которая возвращает уникальный хэш-код.Для достижения наилучших результатов хеш-код должен основываться на значении поля или свойства экземпляра, а не на статическом поле или свойстве.

Так что если вы не перезаписали метод GetHashCode, он может вернутьтот же самый.Я подозреваю, что это происходит потому, что он генерируется из определения, а не из экземпляра.

1 голос
/ 08 июля 2011

Это пахнет как некоторые из случаев, упомянутых в этой теме, возможно, это даст вам некоторые советы по этому поведению.иначе вы можете записать это там: -)

Какой самый странный угловой случай вы видели в C # или .NET?

Rgds GJ

...