I was just looking at this answer, which contains the code for Nullable<T>
from .NET Reflector, and I noticed two things:
Nullable<T>
to T
.==
operator is not defined.Given these two facts, it surprises me that this compiles:
int? value = 10;
Assert.IsTrue(value == 10);
With the code value == 10
, either value
is being magically converted to an int
(hence allowing int
's ==
operator to be used, or the ==
operator is being magically defined for Nullable<int>
. (Or, I presume less likely, Reflector is leaving out some of the code.)
I would expect to have to do one of the following:
Assert.IsTrue((value.Equals(10)); // works because Equals *is* defined
Assert.IsTrue(value.Value == 10); // works because == is defined for int
Assert.IsTrue((int?)value == 10); // works because of the explicit conversion
These of course work, but ==
also works, and that's the part I don't get.
The reason I noticed this and am asking this question is that I'm trying to write a struct that works somewhat similarly to Nullable<T>
. I began with the Reflector code linked above, and just made some very minor modifications. Unfortunately, my CustomNullable<T>
doesn't work the same way. I am not able to do Assert.IsTrue(value == 10)
. I get "Operator ==
cannot be applied to operands of type CustomNullable<int>
and int
".
Now, no matter how minor the modification, I would not expect to be able to do...
CustomNullable<T> value = null;
...because I understand that there is some compiler magic behind Nullable<T>
that allows values to be set to null
even though Nullable<T>
is a struct, but I would expect I should be able to mimic all the other behaviors of Nullable<T>
if my code is written (almost) identically.
Can anyone shed light on how the various operators of Nullable<T>
work when they appear not to be defined?
Some int value as an int? is definitely non-null and null is definitely null. The compiler realizes that and since a non-null value is not equal to a definite null value, the warning is given. The compiler also optimizes this away because it is always false. It won't even load the x variable at all.
By using the null-forgiving operator, you inform the compiler that passing null is expected and shouldn't be warned about. You can also use the null-forgiving operator when you definitely know that an expression cannot be null but the compiler doesn't manage to recognize that.
If you want to use the default value of the underlying value type in place of null , use the Nullable<T>. GetValueOrDefault() method. At run time, if the value of a nullable value type is null , the explicit cast throws an InvalidOperationException.
The HasValue property returns true if the variable contains a value, or false if it is null. You can only use == and != operators with a nullable type. For other comparison use the Nullable static class.
Given these two facts, it surprises me that this compiles
Given only those two facts, that is surprising.
Here's a third fact: in C#, most operators are 'lifted to nullable'.
By "lifted to nullable", I mean that if you say:
int? x = 1;
int? y = 2;
int? z = x + y;
then you get the semantics of "if either x or y is null then z is null. If both are not null then add their values, convert to nullable, and assign the result to z."
The same goes for equality, though equality is a bit weird because in C#, equality is still only two-valued. To be properly lifted, equality ought to be three-valued: x == y should be null if either x or y is null, and true or false if x and y are both non-null. That's how it works in VB, but not in C#.
I would expect I should be able to mimic all the other behaviors of
Nullable<T>
if my code is written (almost) identically.
You are going to have to learn to live with disappointment because your expectation is completely out of line with reality. Nullable<T>
is a very special type and its magical properties are embedded deeply within the C# language and the runtime. For example:
C# automatically lifts operators to nullable. There's no way to say "automatically lift operators to MyNullable". You can get pretty close by writing your own user-defined operators though.
C# has special rules for null literals -- you can assign them to nullable variables, and compare them to nullable values, and the compiler generates special code for them.
The boxing semantics of nullables are deeply weird and baked into the runtime. There is no way to emulate them.
Nullable semantics for the is
, as
and coalescing operators are baked in to the language.
Nullables do not satisfy the struct
constraint. There is no way to emulate that.
And so on.
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