Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When Implementing IEqualityComparer Should GetHashCode check for null?

Tags:

c#

hash

When implementing IEqualityComparer<Product> (Product is a class), ReSharper complains that the null check below is always false:

public int GetHashCode(Product product)
{
  // Check whether the object is null. 
  if (Object.ReferenceEquals(product, null))
    return 0;

  // ... other stuff ...
}

(Code example from MSDN VS.9 documentation of Enumerable.Except)

ReSharper may be wrong, but when searching for an answer, I came across the official documentation for IEqualityComparer<T> which has an example where null is not checked for:

public int GetHashCode(Box bx)
{
    int hCode = bx.Height ^ bx.Length ^ bx.Width;
    return hCode.GetHashCode();
}

Additionally, the documentation for GetHashCode() states that ArgumentNullException will be thrown when "The type of obj is a reference type and obj is null."

So, when implementing IEqualityComparer should GetHashCode check for null, and if so, what should it do with null (throw an exception or return a value)?

I'm interested most in .NET framework official documentation that specifies one way or another if null should be checked.

like image 528
zastrowm Avatar asked Jun 27 '14 20:06

zastrowm


4 Answers

There is some nuance to this question.

The docs state that IEqualityComparer<T>.GetHashCode(T) throws on null input; however EqualityComparer<>.Default - which is almost certainly by far the most used implementation - does not throw.

Clearly, an implementation does not need to throw on null it merely has the option too.

However, I'd argue that no implementation should ever throw on null here, it's just confusing, and a possible source of bugs. Exceptions are a pain in any case, being a non-local control flow mechanism, and that alone argues for using them when necessary only (i.e.: not here). But additionally, for IEqualityComparer specifically, the docs state that whenever Equals(x, y) then GetHashCode(x) should equal GetHashCode(y) - and Equals does allow nulls, and is not documented as throwing any exceptions.

The invariant that equality implies hashcode equality makes implementing things relying on those hashcodes much simpler. Having a gotcha with the null value is a design cost you should avoid paying without need. And here there is no need, ever.

In short:

  • do not throw from GetHashCode, even though it is allowed
  • and do check for nulls; Resharper's warning is incorrect.

Doing this results in simpler code with fewer gotchas, and it follows the behavior of EqualityComparer<>.Default which is the most common implementation used.

like image 167
Eamon Nerbonne Avatar answered Oct 01 '22 20:10

Eamon Nerbonne


ReSharper is wrong.

Obviously code you write can call that particular GetHashCode method and pass in a null value. All known methods might ensure this will never happen, but obviously ReSharper can only take existing code (patterns) into account.

So in this case, check for null and do the "right thing".


Corollary: If the method in question was private, then ReSharper might analyze (though I'm not sure it does) the public code and verify that there is indeed no way that this particular private method will be called with a null reference, but since it is a public method, and one available through an interface, then

ReSharper is wrong.

like image 31
Lasse V. Karlsen Avatar answered Oct 01 '22 21:10

Lasse V. Karlsen


The documentation says that null values should never be hashable, and that attempting to do so should always result in an exception.

Of course, you're free to do whatever you want. If you want to create a hash based structure for which null keys are valid, you're free to do so, in this case you should simply ignore this warning.

like image 37
Servy Avatar answered Oct 01 '22 20:10

Servy


ReSharper has some special case code here. It will not warn about the ReferenceEquals in this:

if (ReferenceEquals(obj, null)) { throw new ArgumentNullException("obj"); }

It will warn about the ReferenceEquals in this:

if (ReferenceEquals(obj, null)) { return 0; }

Throwing an ArgumentNullException exception is consistent with the contract specified in IEqualityComparer(Of T).GetHashCode

If you go to the definition of IEqualityComparer (F12) you'll also find further documentation:

    // Exceptions:
    //   System.ArgumentNullException:
    //     The type of obj is a reference type and obj is null.
    int GetHashCode(T obj);

So ReSharper is right that there is something wrong, but the error displayed doesn't match the change you should make to the code.

like image 27
Denise Skidmore Avatar answered Oct 01 '22 22:10

Denise Skidmore