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.
Related
If we use the == operator between an expression of ulong and an expression of ulong?, then the operator overload bool ulong(ulong left, ulong right) appears to be used.
In other words, the operator considers both expressions non-null.
In this sample program, equal correctly becomes false, with no exceptions.
void Main()
{
var temp = new Temp(0);
object temp2 = null;
var equal = temp.Id == (temp2 as Temp)?.Id; // False :) but how?
}
public class Temp
{
public ulong Id {get;}
public Temp(ulong id)
{
this.Id = id;
}
}
How can this not throw? There is no conversion from a ulong? with value null to a ulong. (ulong)(ulong?)null throws: "Nullable object must have a value."
Does this return the correct value for every possible value of ulong?, including null? If so, how? The type ulong? has one more possible value than ulong, so there should be one set of two values that map to the same ulong value, which would introduce one false positive "true" result.
In theory, I could imagine null being coalesced to default(ulong), but then the result in my example above would be true, which would be an incorrect answer. And as we can see, the compiler does not make that mistake - it answers correctly.
From MSDN:
Lifted operators permit predefined and user-defined operators that operate on non-nullable value types to also be used with nullable forms of those types. Lifted operators are constructed from predefined and user-defined operators that meet certain requirements, as described in the following:
...
For the equality 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 considers two null values equal, and a null value unequal to any non-null value. If both operands are non-null, the lifted operator unwraps the operands and applies the underlying operator to produce the bool result.
You're not using the operator:
bool ==(ulong left, ulong right)
You're using the lifted operator:
bool ==(ulong? left, ulong? right)
This operator takes two ulong? parameters, and returns true if both are null, or if both are non-null and have the same value.
You're probably looking at Visual Studio, which does show you something confusing in this case:
Don't be confused by this -- as #mjwills pointed out in the comments, this is a known issue.
If you write this:
public bool M(ulong a, ulong? b) {
return a == b;
}
Then the compiler produces the following code:
public bool M(ulong a, ulong? b)
{
ulong? num = b;
return (a == num.GetValueOrDefault()) & num.HasValue;
}
num.GetValueOrDefault() returns 0 if b is null, otherwise the value of b. So M returns true if and only if b is not null, and has the same value as a.
SharpLab
If we use the == operator between an expression of ulong and an
expression of ulong?, then the operator overload bool ulong(ulong
left, ulong right) is used.
A large part of the issue is that Visual Studio's intellisense incorrectly shows ulong's == operator being used (if you hover over ==).
This is a bug, as per https://github.com/dotnet/roslyn/issues/21494 . It is not actually using that operator.
Since all of your other questions are based on that faulty premise, realising the existence of that bug makes the other issues largely disappear.
The C# 5.0 spec reads in chapter 7.1.3
https://msdn.microsoft.com/en-us/library/ms228593.aspx
The lifted operator produces the value false if one or both operands are null.
However testing and also this MSDN link
http://msdn.microsoft.com/en-us/library/2cf62fcy(v=vs.100).aspx
int? num1 = 10;
int? num2 = null;
// Change the value of num1, so that both num1 and num2 are null.
num1 = null;
if (num1 == num2)
{
// The equality comparison returns true when both operands are null.
Console.WriteLine("num1 == num2 returns true when the value of each is null");
}
/* Output:
* num1 == num2 returns true when the value of each is null
*/
shows that comparing two nullable values that are both null returns true.
It makes sense but it contradicts the sentence from the spec, does it not?
Dont mix, this is about different types of operators.
• For the equality 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 considers two null values equal, and a null value unequal to any non-null value. If both operands are non-null, the lifted operator unwraps the operands and applies the underlying operator to produce the bool result.
• 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.
Spec later says that
• For the equality 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 considers two null values equal, and
a null value unequal to any non-null value. If both operands are
non-null, the lifted operator unwraps the operands and applies the
underlying operator to produce the bool result.
I have something like the following:
new ObservableCollection<SomeData>(SomeDataCollection.Where(a => a.AGD_Category.Equals((int)AGD_CategoryEnum.Med) && ((a.AGG_DateStart.Date <= SelectedUntill) && (a.AGG_DateEnd.Value.Date >= SelectedFrom))));
now my a.AGG_DateEnd is a nullable datetime and whenever there is a null value in the list I get an exception because of the "a.AGG_DateEnd.Value.Date" part.
But the thing is, the a.AGG_DateEnd also contains hours and minutes, but I do not want this causes my comparison to give unreliable results.
What can I do to improve?
Thank you,
A quick hack is:
a.AGG_DateEnd.GetValueOrDefault().Date >= SelectedFrom
This is OK because the default(DateTime) (which equals its own .Date) is not greater than any (other) DateTime value.
Otherwise the usual thing to do is:
a.AGG_DateEnd.HasValue && a.AGG_DateEnd.Value.Date >= SelectedFrom
where the double & ensures short-cut evaluation.
Addition after comments:
If you want the opposite answer in the case where it is null, it is:
(a.AGG_DateEnd ?? DateTime.MaxValue).Date >= SelectedFrom
respectively:
!a.AGG_DateEnd.HasValue || a.AGG_DateEnd.Value.Date >= SelectedFrom
Later edit: Since C# 6.0 (July 2015), you can use the ?. operator instead. So use the shorter code:
a.AGG_DateEnd?.Date >= SelectedFrom
which will (formally, at least) compare two nullable values of type DateTime?. When either operand is "null" (i.e. .HasValue gives false), such a comparison gives false.
If you want the opposite bool result in case of null, that is:
!(a.AGG_DateEnd?.Date < SelectedFrom)
Observe how x >= y is different from !(x < y) when we use these lifted operators that operate on Nullable<> values. Any lifted operator (like >= or <) will return false immediately if one of x and y is null; only if both x and y are non-null, will they be unwrapped and the contents compared with the non-lifted operator whose return value will be used.
I have recently noticed a curiosity (at least for me). I thought that the null-coalescing operator would take the precedence over any mathematical operation but obviously i was wrong. I thought following two variables would have the same value at the end:
double? previousValue = null;
double? v1 = 1 + previousValue ?? 0;
double? v2 = 1 + (previousValue ?? 0);
But v2.Value is (the desired) 1 whereas v1.Value is still 0. Why?
Demo
v1 is 0 for the exact reason you mentioned: the null-coalescing operator actually has relatively low precedence. This table shows exactly how low.
So for the first expression, 1 + null is evaluated first, and it evaluates to a null int?, which then coalesces to 0.
v2 is saying,1 plus (if previousValue == null add to 1 the value 0,which gives 1.The
v1 is saying 1 plus null is null so lets give back the 0.
int? x = null;
x = x + 1; // Works, but x remains null
I would expect the compiler to attempt to cast x as an int, but apparently it does not.
Edit by 280Z28: Changed NullReferenceException to InvalidOperationException, which is what Nullable<T>.Value throws when HasValue is false.
This is per the specification for lifted binary operators. From §7.2.7:
For the binary operators
+ - * / % & | ^ << >>
a lifted form of an operator exists if the operand and result types are all non-nullable value types. The lifted form is constructed by adding a single ? modifier to each operand and result type. The lifted operator produces a null value if one or both operands are null (an exception being the & and | operators of the bool? type, as described in §7.10.3). Otherwise, the lifted operator unwraps the operands, applies the underlying operator, and wraps the result.
The reasoning is this: you are to think of null for a nullable type as meaning "I do not know what the value is." What is the result of "I don't know" plus one? "I don't know." Thus, the result should be null.
Nullables are never actually null references. They are always object references. Their internal classes override the == and = operators. If they are being compared to null, they'll return the value of the HasValue property.
Why would you expect the compiler to cast it as int when you've declared it as Nullable? The compiler is doing what you've told it to do and null +1 = null.
You'll have to cast explicitly or check x.HasValue before attempting to add an int.
The reason for this is that the compiler creates a 'lifted' operator for nullable types - in this case it is something like:
public static int? operator +(int? a, int? b)
{
return (a == null || b == null) ? (int?)null : a.Value + b.Value
}
I think if you try to assign the result to a non-nullable value, the compiler will be forced to use the non-nullable overload and convert x to an int.
e.g. int i = x + 1; //throws runtime exception
Unfortunately it doesn't. The X in x = X + 1 is null as in the first line so you're adding 1 to null, which equals null.
As it's a nullable int, you can use x.HasValue to check if it has a value, and then x.Value to get the actual int value out
Regardless of whether x is actually never null, that's not even the point.
The point is, when have you ever seen a NullReferenceException when trying to perform an addition?
The following example doesn't throw a NullReferenceException either and is perfectly valid.
string hello = null;
string world = "world";
string hw = hello+world;
You would only get a NullReferenceException if you try to access a member on an object that is null.
int? can never be null because it is a struct. Structs live on the stack and the stack does not handle null well.
See What is a NullPointerException, and how do I fix it?
Also, the nullable types have 2 very useful properties : HasValue, Value
This code:
if (x != null)
{
return (int) x;
}
Should be refactored to this:
if (x.HasValue)
{
return x.Value;
}