39

Bit of a weird question...

But can anyone give me a justification for why this would be expected behaviour?

This just seems totally odd to me....

//Makes perfect sense
object o = null;
o.GetHashCode().Dump();

NullReferenceException: Object reference not set to an instance of an object.

//Seems very odd
int? i = null;
i.GetHashCode().Dump();

0

This obviously means:

int? zero = 0;
int? argh = null;

zero.GetHashCode() == argh.GetHashCode(); //true
blahdiblah
  • 33,069
  • 21
  • 98
  • 152
Dave Bish
  • 19,263
  • 7
  • 46
  • 63
  • 13
    Good question. However be aware that only because two hashcodes are equal doesn´t mean the two *instances* are equal also. – MakePeaceGreatAgain Feb 12 '18 at 14:01
  • Interestingly boxing the nullable int to `object` and calling `GetHashCode` again yields to an NRE. – MakePeaceGreatAgain Feb 12 '18 at 14:03
  • 13
    This question is over my head so apologies for my ignorance, but for my benefit the documentation says of `Nullable.GetHashCode`: `The hash code of the object returned by the Value property if the HasValue property is true, or zero if the HasValue property is false.`. Since `HasValue` here is `false`, I'd expect 0. Isn't this the answer? – Equalsk Feb 12 '18 at 14:06
  • If you mean "why does it not throw", then your question's [a duplicate of this](https://stackoverflow.com/q/5547808/4137916). Well, related, really, not exactly a duplicate. I suppose it *could* have an explicit `throw new NullReferenceException` in there, as confusing as that would be. – Jeroen Mostert Feb 12 '18 at 14:10
  • 4
    int? is a struct, syntax sugar for `Nullable`. A struct always has a hash code and never produces NRE. – Hans Passant Feb 12 '18 at 14:11
  • Follow up question for you to consider: what does `GetType` do when its receiver is a null `int?` vs what does it do when its receiver is a non-null `int` -- can you predict what the program will do before you run it? Now try it; was your prediction correct? Are you surprised? – Eric Lippert Feb 12 '18 at 15:53
  • 10
    There are `2^32` possible hash codes, and `2^32 + 1` possible nullable ints, so *at least* two `int?`s have to have the same hash code. It ought not to be that big a surprise that the pair with equal hash codes are 0 and null. – Eric Lippert Feb 12 '18 at 16:01

3 Answers3

48

The point here is that

int? i = null;

does not create a variable i which is null, but (by performing an implicit cast) a Nullable<int> instance which does not have a value.
This means the object/instance is not null (and as Nullable<T> is a struct/value type it actually can't be null) and therefore has to return a hash-code.

This is also documented here:

The hash code of the object returned by the Value property if the HasValue property is true, or zero if the HasValue property is false.

psmears
  • 26,070
  • 4
  • 40
  • 48
Christoph Fink
  • 22,727
  • 9
  • 68
  • 113
  • 4
    You might want to explain that it's because `Nullable` is a value type... – Zohar Peled Feb 12 '18 at 14:05
  • Ok - thanks for linking to the docs. I imagine some language designers argued about this for 2 weeks before agreeing the implementation - Really, GetHashCode should return Nullable() somehow :) – Dave Bish Feb 12 '18 at 14:12
  • 1
    Object cant be null, but if you ask if it is null `i == null` it will return true :-) – Magnus Feb 12 '18 at 14:17
15

int? is actually just shorthand for Nullable<int>, a struct which wraps the int type in order to allow it to be null. Nullable can be used with any value type.

Because Nullable is actually a struct (it can't be null), it has to return something for a hash code, and normally it would return the value's hash code (presumably in order to be as transparent as possible to the value within). When the value is null, it is hard-coded to return 0 by default:

public override int GetHashCode() {
    return hasValue ? value.GetHashCode() : 0;
}

See here.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
  • 2
    It could throw the same NullReferenceException, instead - which IMO would be more correct, – Dave Bish Feb 12 '18 at 14:08
  • 10
    If it threw a NullReferenceException and you had a `HashSet`. You can't add a null value to it. You would get a NullReferenceExeption. This isn't really expected behaviour - why would you use a HashSet if it can't accept a null value? You would simply use HashSet. [Example](https://dotnetfiddle.net/Jfhu8G). It makes more sense in the general framework if it doesn't throw an exception and better fits the idea of using Nullable, IMHO. – ProgrammingLlama Feb 12 '18 at 14:15
  • 4
    @john, what happens when you add `null` to a `HashSet`? – Arturo Torres Sánchez Feb 12 '18 at 17:26
  • 3
    So, whether it throws a NullReferenceException or not is inconsequential, or else it would fail on the null string case. – Arturo Torres Sánchez Feb 13 '18 at 01:15
  • 1
    @Arturo I concede. You're right. The [HashSet](https://github.com/dotnet/corefx/blob/master/src/System.Collections/src/System/Collections/Generic/HashSet.cs#L1712-L1719) code also has the same "if null, hashcode is 0" logic within it, and a null nullable would obviously evaluate to true there. So, even if GetHashCode threw a NullReferenceException, it would still work as expected. – ProgrammingLlama Feb 13 '18 at 01:33
8

It looks like documented behaviour of Nullable<T>.GetHashCode(), as documentation says:

The hash code of the object returned by the Value property if the HasValue property is true, or zero if the HasValue property is false.

gabba
  • 2,815
  • 2
  • 27
  • 48