During a recent code review, I came across something that made me say, "wait, that's not right; how does this even compile?"
Let's say I have this C# code:
Does this compile? No, of course not. We're comparing
Foo (a value-type) to
null, and of course you can't do that.
SharpLab tells us
error CS0019: Operator '==' cannot be applied to operands of type 'Foo' and '<null>'
As it very well should. But what if I make a simple change? I'll add a couple of useful methods to
Now, SharpLab tells us
This is the for-illustrative-purposes version of what my coworker had written in the code I was reviewing. But how can this work? All I did was add some comparison operators, which you should define for all your structs, right?
Alright, so what's going on? Here's a hint: what if, instead, I had written this:
No problem here because I can
- Construct a
Nullable<T>from a value-type
- Compare a
Of course, in this case,
foo will always have a value, because I constructed it with one.
What's this got to do with the fist example? Because I have used
==, the compiler searches for a suitable operator that can handle the operands. It can't compare
Foo directly with
null, but it does manage to find that there is an
== operator for
null, which just requires that it can compare two
Ts using the
== operator. So all it has to do is promote value-type
Foo to a
Nullable<Foo> in order to perform the comparison with
However, I don't think I'm alone in experiencing some surprise that the compiler does this conversion for you as a consequence of implementing
And since the promoted
Nullable<Foo> always has a value,
foo == null will never be true. The compiler can optimize that pretty easily. Let's take a look at the generated IL for
Note the comparison and branch have been completely removed. It unconditionally prints
Foo is NOT null
However, the compiler won't warn you about this, and if you ever write it, it probably wasn't what you meant.