Почему два экземпляра делегата возвращают один и тот же хэш-код?
Возьмите следующее:
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
Ответы
Ответ 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, возвращаем значения последнего делегата элемента).
Но я не совсем уверен, выполняет ли он контракт во всех угловых случаях.
Хм, похоже, качество требует ссылочного равенства целей.
Ответ 3
Это пахнет некоторыми случаями, упомянутыми в этом потоке, возможно, это даст вам несколько указаний на это поведение. иначе вы можете его зарегистрировать: -)
Какой самый странный угловой случай, который вы видели на С# или .NET?
Rgds GJ
Ответ 4
Из MSDN:
Стандартная реализация GetHashCode не гарантирует уникальность или последовательность; следовательно, он не должен использоваться как уникальный объект идентификатор для целей хеширования. Производные классы должны переопределять GetHashCode с реализацией который возвращает уникальный хэш-код. Для наилучшие результаты, хэш-код должен быть основанный на значении экземпляра поле или свойство вместо статического поле или свойство.
Итак, если вы не перезаписали метод GetHashCode, он может вернуться к нему. Я подозреваю, что это происходит потому, что он генерирует его из определения, а не из экземпляра.