Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird nullable comparison behavior

What is the rational (if any) behind the following behavior:

int? a = null;
Console.WriteLine(1 > a); // prints False
Console.WriteLine(1 <= a); // prints False
Console.WriteLine(Comparer<int?>.Default.Compare(1, a)); // prints 1

Why are the comparison operators behaving differently from the default comparer for nullables?

More weirdness:

var myList = new List<int?> { 1, 2, 3, default(int?), -1 };
Console.WriteLine(myList.Min()); // prints -1 (consistent with the operators)
Console.WriteLine(myList.OrderBy(i => i).First()); // prints null (nothing) (consistent with the comparator)

Console.WriteLine(new int?[0].Min()); // prints null (nothing)
Console.WriteLine(new int[0].Min()); // throws exception (sequence contains no elements)
like image 873
ChaseMedallion Avatar asked Oct 21 '22 05:10

ChaseMedallion


1 Answers

<= and > are lifted operators which return false if either value is null.

For the relational operators

< > <= >=

a lifted form of an operator exists if the operand types are both non-nullable value types and if the result type is bool. The lifted form is constructed by adding a single ? modifier to each operand type. The lifted operator produces the value false if one or both operands are null. Otherwise, the lifted operator unwraps the operands and applies the underlying operator to produce the bool result.

Since comparers are used for sorting they need a total ordering where null by definition compares as less than every other value. This need takes precedence over consistency with comparison operators. Returning 0 when comparing null with any other value isn't possible this that would violate transitivity, so designers had to choose to either emit an error, or sort always sort null as smaller or larger than any other value. In .net 1 they decided that null is smaller than everything else was decided in for reference types and naturally that decision carried over to nullable value types in .net 2.

The difference between these is pretty similar to how NaN behaves on floating points. For example NaN doesn't doesn't even equal itself and all comparison operators return false. But when using a comparer NaN is equal to itself and smaller than other value except null.

Enumerable.Min returns the smallest non-null value, and only returns null if the sequence contains no non-null values. With such a function null typically stands for an omitted value and you're interested in finding the smallest real value.

like image 82
CodesInChaos Avatar answered Oct 23 '22 20:10

CodesInChaos