devuxer
devuxer

Reputation: 42344

Why does the == operator work for Nullable<T> when == is not defined?

I was just looking at this answer, which contains the code for Nullable<T> from .NET Reflector, and I noticed two things:

  1. An explicit conversion is required when going from Nullable<T> to T.
  2. The == 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?

Upvotes: 16

Views: 2792

Answers (5)

Eric Lippert
Eric Lippert

Reputation: 659974

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.

Upvotes: 36

Arnold Zokas
Arnold Zokas

Reputation: 8560

Because the compiler converts Nullable<T> to T and then performs the comparison.

Upvotes: 1

THX-1138
THX-1138

Reputation: 21730

Nullable<T> has this method:

public static implicit operator T?(T value)
{
  return new T?(value);
}

It looks like there is an implicit conversion from it 10 to Nullable<int> 10

To make == / != work for you. You can add

public static bool operator ==(MyNullable<T> left, MyNullable<T> right) {}
// together with:
public static implicit operator MyNullable<T>(T value) {}

should give you support for myNullable == 10 operation

Upvotes: 1

Andrew Savinykh
Andrew Savinykh

Reputation: 26270

Well, if you can use reflector why don't you compile this code:

int? value = 10;
Console.WriteLine(value == 10);

and then open it in reflector? You'll see this (make sure to select 'None' as .net version to decompile to):

int? value;
int? CS$0$0000;
&value = new int?(10);
CS$0$0000 = value;
Console.WriteLine((&CS$0$0000.GetValueOrDefault() != 10) ? 0 : &CS$0$0000.HasValue);

So basically the compiler does the heavy lifting for you. It understands what '==' operation means when used with nullables and compiles the necessary checks accordingly.

Upvotes: 4

Jonathan Allen
Jonathan Allen

Reputation: 70307

This is language dependent. C# and Visual Basic emit different code when dealing with operators on nullable value types. To udnerstand it you need to look at the actual IL code.

Upvotes: 1

Related Questions