Подтвердить что ты не робот

Почему два экземпляра делегата возвращают один и тот же хэш-код?

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

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

Update:

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

Лучшая реализация hashcode была бы:

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

Ответ 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. Следовательно, приведенный выше результат правильный.

UPDATE

Ответ 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;
    }
}

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

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

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

Ответ 4

Из MSDN:

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

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