What does RuntimeHelpers.GetHashCode do
Asked Answered
M

4

27

The RuntimeHelpers.GetHashCode(object) method allows generating hash codes based on the identity of an object. MSDN states:

The RuntimeHelpers.GetHashCode method always calls the Object.GetHashCode method non-virtually, even if the object's type has overridden the Object.GetHashCode method.

[MethodImpl(MethodImplOptions.InternalCall)]
[SecuritySafeCritical]
public static extern int GetHashCode(object o);

However, when inspecting the Object.GetHashCode() method using Reflector (.NET 4.0), we'll see the following code:

public virtual int GetHashCode()
{
    return RuntimeHelpers.GetHashCode(this);
}

This makes me believe that the MSDN documentation is wrong, since calling Object.GetHashCode from within the RuntimeHelpers.GetHashCode(object) would cause a stack overflow.

So what is the actual behavior of RuntimeHelpers.GetHashCode(object) and how does it work? How does it calculate the hash?

Malady answered 28/6, 2012 at 7:33 Comment(1)
@dtb there are some places where C# makes a non-virtual call to a virtual method - base.* for example. In most scenarios, using call on a virtual method will make the CLI shout loudly at you - it isn't allowed, regardless of the language of the caller.Interplead
I
30

I think the MSDN documentation is trying to describe the behaviour, not the implementation. The key point: RuntimeHelpers returns the default implementation that you would get were object.GetHashCode() not overridden.

This is really useful if, for example, you want to build a reference equality lookup, even for types that have overridden Equals and GetHashCode. I do this in a serializer that I maintain, using RuntimeHelpers.GetHashCode() and Object.ReferenceEquals.

Interplead answered 28/6, 2012 at 7:39 Comment(7)
So, the documentation is a bit misleading, or am I just interpretering it incorrectly?Malady
@Malady yes, saying that it "calls the Object.GetHashCode method non-virtually" is a bit misleading, especially given the two other implementations shown here (in your post, and "Me.Name"'s post)Interplead
@Jon yes, but based on the two reflector dumps in this post and one of the answers, it isn't actually how the IL is implemented.Interplead
@MarcGravell: Yes, and I'm going to elaborate on that in my answer instead of in comments :)Hostelry
@Jon I shall look forward to it ;pInterplead
"you want to build a reference equality lookup". And guess what, that's what I'm trying to do ;-)Malady
I guess there is a typo. It should be RuntimeHelpers instead of RuntimeTypeHelpersLeeuwenhoek
H
14

The point is that object.GetHashCode() can be overridden - and frequently is, e.g. by string. That means you can't find out the "identity hash code" which the default implementation of object.GetHashCode() returns.

This can be useful if you want to implement an equalty comparer (e.g. for a HashSet) which only considers object identity.

For example:

public class IdentityComparer<T> : IEqualityComparer<T> where T : class
{
    public bool Equals(T x, T y)
    {
        // Just for clarity...
        return object.ReferenceEquals(x, y);
    }

    public int GetHashCode(T x)
    {
        // The nullity check may be unnecessary due to a similar check in
        // RuntimeHelpers.GetHashCode, but it's not documented
        return x == null ? 0 : RuntimeHelpers.GetHashCode(x);
    }
}

Then:

string x = "hello";
string y = new StringBuilder("h").Append("ello").ToString();
Console.WriteLine(x == y); // True (overloaded ==)
Console.WriteLine(x.GetHashCode() == y.GetHashCode()); // True (overridden)

IdentityComparer<string> comparer = new IdentityComparer<string>();
Console.WriteLine(comparer.Equals(x, y)); // False - not identity

// Very probably false; not absolutely guaranteed (as ever, collisions
// are possible)
Console.WriteLine(comparer.GetHashCode(x) == comparer.GetHashCode(y));

EDIT: Just to clarify a bit...

So what is the actual behavior of RuntimeHelpers.GetHashCode(object) and how does it work?

The observed behaviour is that the value returned from RuntimeHelpers.GetHashCode(object) is the same as the value which would be returned from a non-virtual call to Object.GetHashCode(). (You can't write that non-virtual call in C# easily.)

As for how it works - that's an implementation detail :) It doesn't really matter IMO which way round things occur (what calls what). The important thing is the documented behaviour, which is correct. Heck, different versions of mscorlib could implement this differently - it wouldn't matter at all from a user's point of view. Without decompilation, you shouldn't be able to tell the difference.

It would (IMO) have been far more confusing for Object.GetHashCode to have been documented in terms of RuntimeHelpers.GetHashCode().

Hostelry answered 28/6, 2012 at 7:40 Comment(4)
RuntimeHelpers.GetHashCode handles null, so there is no need to do it explicitly.Involution
@MattSmith: Hmm... unfortunately that's not documented as far as I can see, so I wouldn't want to rely on it - but I'll add a comment.Hostelry
Good point. I actually considered it "documented" since normally Microsoft's documentation includes a clause in the documentation when it does throw (for example: System.ArgumentNullException: o is null.). But perhaps they are not always diligent.Involution
@MattSmith in fact object.GetHashCode() handles null returning 0 too, if you're using a language that lets you non-virtually call an instance method on a null instance. (AFAIK, for all implementations out there). Of course, C# isn't such a language.Tertiary
M
4

Strange, when I look at System.Object.GetHashCode via Reflector I see

public virtual int GetHashCode()
{
    return InternalGetHashCode(this);
}

and for runtimehelper:

public static int GetHashCode(object o)
{
    return object.InternalGetHashCode(o);
}

Perhaps it's a framework difference? I'm looking at 2.0 assemblies.

Macmillan answered 28/6, 2012 at 7:39 Comment(1)
Don't remove this answer. It shows that the implementation can vary, but the behavior is still as specified. (Your answer was mentioned in Skeet's answer).Narcissus
N
0

From your own question, it looks like RuntimeHelpers.GetHashCode(Object) is really the implementation of the non-overridden Object.GetHashCode().

Narcissus answered 28/6, 2012 at 7:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.