1

Can someone explain to me why this works?

List<int> foo = null;
bool bar = foo?.Count > 7;

When foo is null, it becomes equivalent to the following:

bool bar = null > 7;

The IntelliSense is saying both null and 7 are of type Nullable<int>, but how did the compiler decide that?!

Additionally, Nullable<T> doesn't define a greater than operator. How could it? There is no guarantee that T defines a greater than operator.

Sнаđошƒаӽ
  • 16,753
  • 12
  • 73
  • 90
Nechemia Hoffmann
  • 2,118
  • 2
  • 12
  • 21
  • 2
    Because there's an implicit conversion between `int` and `Nullable`, and `null` to `Nullable`, which the compiler does for us in order to evaluate the condition (both operands have to be of the same type) – Rufus L Aug 05 '20 at 17:53
  • 1
    `Nullable` is magic; operators on types are automatically lifted to the corresponding `Nullable` without needing an explicit operator. The language spec has the details on this, but it's not very accessible (overload resolution is one of the most complicated topics, and to get the full picture of how nullables work you have to read multiple different sections). – Jeroen Mostert Aug 05 '20 at 17:55
  • @RufusL Which way is the conversion going in the above case? – Nechemia Hoffmann Aug 05 '20 at 17:55
  • I don't know what you mean. Both types are converted. See: [Nullable.Implicit(T to Nullable)](https://learn.microsoft.com/en-us/dotnet/api/system.nullable-1.op_implicit?view=netcore-3.1) – Rufus L Aug 05 '20 at 17:56
  • The ? operator checks for null on the preceding object (a List in this case, the generic type does not matter in this example). If null, the CLR will stop execution of the line instead of throwing an exception. You are just getting the default value of boolean. – insane_developer Aug 05 '20 at 17:58
  • @RufusL It seems to me that both types are being converted to `Nullable` that I kind of understand. There is an implicit cast from `T` to `Nullable` defined in the `Nullable` sources. The question is how can you compare the two afterwards. – Nechemia Hoffmann Aug 05 '20 at 17:59
  • 3
    FWIW, [this](https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/expressions#lifted-operators) is the section that explains how operators on nullables work without being explicitly defined. – Jeroen Mostert Aug 05 '20 at 17:59
  • @NechemiaHoffmann Count is a property of the List type. There would be no reason to convert int to anything. List can be null regardless of the type of T. – insane_developer Aug 05 '20 at 18:02
  • This is why I always use `GetValueOrDefault()` because it's confusing what actually happens when the code compiles. – Andy Aug 05 '20 at 18:03
  • I believe that if one of the values is `null`, the comparison will always return `false`. Otherwise it uses the result of the comparison for type `T` (`int` in this case). – Rufus L Aug 05 '20 at 18:07
  • 1
    @Andy: except that's not always an option or not always correct. `null > x` with `x` an `int` is always `false`, but `.GetValueOrDefault()` gives `0`. In this case (a count of items) `0` is a perfectly sensible value, but of course the whole reason to allow nullables in the first place is to allow for the case where there is no sensible value of the type itself. (Also, `??` is a thing so there's rarely any reason to invoke the method.) – Jeroen Mostert Aug 05 '20 at 18:08
  • @JeroenMostert -- in those cases then I would simply do `if(foo != null){ work with foo }` I wouldn't want to assume what is going to happen if `foo` was `null` and have the compiler decide the logic at that point. I want to know the logic that happens and not have *any* doubt in my head. This code is a great example this. – Andy Aug 05 '20 at 18:13
  • 1
    Well, to those of us used to SQL and `NULL`, the behavior is obvious (except that comparing for equality/inequality with `null` does not work as in SQL but in the straightforward way). As always, with common features that are deemed confusing or unintuitive, you will typically need to understand how they work anyway, since nothing prevents other developers from using them. It's not like this is an obscure corner case; it's included in the language (and more specifically LINQ) for a reason. The language spec is clear on what this means, in any case; whether you like using it is another matter. – Jeroen Mostert Aug 05 '20 at 18:18

1 Answers1

1

As Rofus and Jeroin correctly pointed out in comments the conversion between T to T? is always implicit when you do the comparison with "Null" 7 becomes a "Nullable<int>"

bool bar = foo?.Count > 7;

is similar to -

 int? count = null; 
 int? k = 7; // Implicit conversion here!
 var bar =  count > k;

For your second question on the > operator. Yes, while the Nullable<T> struct does not define operators such as <, >, or even ==, still the following code compiles and executes correctly which is similar to your case -

int? x = 3;
int? y = 10;
bool b = x > y;

This is because compiler lifts the greater than operator from underlying value type like -

bool b = (x.HasValue && y.HasValue) ? (x.Value > y.Value) : false;

Operator Lifting or Lifted Operators means you can implicitly use T's operators on T?. Compiler has different set of rules on different set of operators on handling them on Nullable<T> types.

Nechemia Hoffmann
  • 2,118
  • 2
  • 12
  • 21
MBB
  • 1,635
  • 3
  • 9
  • 19