Take the following:
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);
This will print:
True False
Why is the hashcode the same?
It is kinda surprising, and will make using delegates in a Dictionary
as slow as a List
(aka O(n)
for lookups).
Update:
The question is why. IOW who made such a (silly) decision?
A better hashcode implementation would have been:
return Method ^ Target == null ? 0 : Target.GetHashcode(); // where Method is IntPtr
Why? An object's hashCode method must take the same fields into account as its equals method. By overriding the equals method, you're declaring some objects as equal to other objects, but the original hashCode method treats all objects as different. So you will have equal objects with different hash codes.
Combines two values into a hash code.
Easy! Since here is the implementation of the GetHashCode
(sitting on the base class Delegate
):
public override int GetHashCode() { return base.GetType().GetHashCode(); }
(sitting on the base class MulticastDelegate
which will call above):
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; }
Using tools such as Reflector, we can see the code and it seems like the default implementation is as strange as we see above.
The type value here will be Action
. Hence the result above is correct.
My first attempt of a better implementation:
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; } }
The quality of this should be good for single cast delegates, but not so much for multicast delegates (If I recall correctly Target/Method return the values of the last element delegate).
But I'm not really sure if it fulfills the contract in all corner cases.
Hmm it looks like quality requires referential equality of the targets.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With