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)
<=
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.
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