Comparing two null nullables - c#

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.

Related

C# comparing reference and value types with null

How does it work?
public class Node
{
Node Left ;
Node Right ;
}
...
var a = new Node();
var b = new Node();
if (a.Right == b.Right == null) // <- compiles and resharper says it's always false
{
}
var aa = 1;
var bb = 2;
if (aa == bb == 0) // doesn't compile Cannot apply operator '==' to operands of type 'bool' and 'int'
{
}
Why do multiple comparands work with 'Node' and doesn't work with 'Int'?
An expression like
a == b == c
is interpreted as
(a == b) == c
so first a == b is evaluated and returns either true or false, a boolean.
Then someBool == c is evaluated. If c is 0, then you are comparing a bool against an int, which leads to your compile error.
However if c is null, then both that null and that "someBool" are promoted lifted to a Nullable<bool> (or bool?), so they can be compared. But of course a true or false is never equal to (bool?)null, which is what Resharper says.
So you cannot take this shortcut and will have to write it as
(a == c) && (b == c)
Or in your case:
if (a.Right == null && b.Right == null) ...
if (aa == 0 && bb == 0) ...
In short, you can't compare like this, ever...
In regards to if (a.Right == b.Right == null)
It compiles because you can compare a non-nullable value types with null and it's always false.
a.Right == b.Right // bool
bool == null //will be lifted and always equal false (see below)
To know the reason why you'll have to visit the specs and understand lifted operators
12.4.8 Lifted operators
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
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.
In regards to if(aa == bb == 0) you can't compare bool with an int, there is no implicit conversion to bool
aa == bb // bool
bool == 0 // there is no implicit conversion to bool
Why do multiple comparands work with 'Node' and doesn't work with
'Int'?
A) I guess you should be asking why it works with Null and not Int values. In your example above Node is a reference type and integer 0 is a value type. Read details below
All Value Types can be compared to null because they are convertible to nullable and null is the default value assigned to a Reference Types
Value types include : Bool, Integers, Floating points, char
Reference types include : Class, string, objects, interface
Valid Cases:
bool a =true;
if(a == null) //a is converted to a Nullable Boolean and then compared with null`
if(a.Right==b.Right==null) //First 2 statements are evaluated to boolean, which is converted to a Nullable Boolean and then compared with null`
int i=5;
if(i==null) // i is converted to a Nullable int and then compared with null, which always ends up false as the value of i is 5
Nullable<int> ii = 5;
if(ii==null) // ii being a nullable int is compared with null, there will not be any warnings indicated by the compiler as it know before hand that ii has a chance of being null
int a =5; int b=4;
if(a==b) // Valid, because your comparing 2 Integers are equal
Invalid Cases:
int a =5; int b=4;
if(a==b==0) // First 2 statements evaulate to a boolean and a boolean is evaluated to a Integer, the compiler fails to understand how to convert a Boolean to Int and hence the error
if(a==true) // same as above, compiler fails to compare an integer to a boolean and results in an error

"ulong == ulong?" evaluated as "ulong == ulong" works correctly

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.

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

C# conditional operator ?: has problems with nullable int [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Conditional operator assignment with Nullable<value> types?
Why does the conditional operator “?:” not work here when my function is returning a nullable integer “int?”? “return null” works but with “?:” I have to cast “null” to “(int?)” first.
public int? IsLongName(string name) {
int length = name.Length;
// this works without problems
if (name.Length > 10) {
return null;
} else {
return name.Length;
}
// this reports:
// Type of conditional expression cannot be determined because
// there is no implicit conversion between '<null>' and 'int'
return name.Length > 10 ? null : name.Length;
}
Try changing your last line to this:
return name.Length > 10 ? null : (int?)name.Length;
The compiler can't understand what's the return type of the ?: operator. It has conflicting values - null and int. By casting the int to nullable, the compiler can understand the return type is nullable int, and null would be accepted as well.
Both a null value and an int value can be implicitly converted to an int? data type, but a literal of null on its own isn't known by the compiler to be anything other than an object if you don't tell it. There is no common data type that both object and int can be implicitly converted to, which is what the compiler is complaining about.
As Yorye says, you can cast the int to int? to let the compiler do the conversion; or, you can cast the null to int?, which then allows the compiled to use the implicit conversion from int to int?.
Both conditions of the ?: operator must be implicitly compatible. int can never be null, so there is a compile-time error (likewise, null can never be int). You have to cast, there's no way around it using the ternary.
I don't think you would have the same problem with an if statement, because the compiler would only check that the method returns a compatible value to the return type from any given path, not that the return value from any given exit point is implicitly compatible with another block's return value.
The ?: operator only considers the types of its two possible return values. It is not aware of the type of the variable that will receive its result (indeed, in more complex expressions, no explicit variable may exist).
If one of those return values is null, it has no type information - it can only check the other return value's type, and check whether a conversion exists.
In your case, we have null and a return value of type int. There is no conversion available. There would be a conversion to int?, but that is neither of the possible return types that ?: is considering.

Why doesn't attempting to add to a null value throw an InvalidOperationException?

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;
}

Categories

Resources