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.
Related
I recently found some code with nullable if condition like this:
bool? nullableVariable;
//some lines of code involving nullableVariable
if (nullableVariable == true)
{
//some other lines of code
}
at first sight I thought it as potentially exception prone. My opinion was based on the fact that:
bool? nullableVariable = null;
bool variable = (bool)nullableVariable
would raise error System.InvalidOperationException: 'Nullable object must have a value.' and I expected that inside the if (nullableVariable == true) statement a cast of left operand from bool? to bool would be performed (my impression confirmed by the fact that moving cursor on == operator results in Visual Studio hint bool bool.operator == (bool left, bool right)).
Given that, I've been surprised seeing that:
bool? nullableVariable = null;
if (nullableVariable == true)
{
//some other lines of code
}
would not raise exception (skipping the code inside brackets).
So the question is: what is happening inside that if statement? maybe a hidden try-catch cast of left operand from bool? to bool with catch setting the result to false? or maybe right operand cast from bool to bool?? or something else?
See lifted operators in Nullable value types
I short, they are overloaded for comparisons like ==:
For the equality operator ==, if both operands are null, the result is
true, if only one of the operands is null, the result is false;
otherwise, the contained values of operands are compared.
So by comparing with true or false you can avoid handling the case that the nullable is null which makes the code more readable like in your example:
if (nullableVariable == true)
{
//some other lines of code
}
So whenever you compare(== or !=) a bool? with a bool the result is not a bool?(or null) but a bool with the rules described in the link above. That's why you can use it in the if.
As mentioned by others, the equality operators are lifted to the nullable equivalents.
There does seem to be slight differences in the way it is compiled. I'm not entirely sure why, but it may be to do with lack of compiler knowledge whether an operator is side-effecting for complex structs, whereas types like int and bool it knows what's going on.
Where A and B are nullables then,
A == B
This is compiled to the following for basic types like int:
(A.GetValueOrDefault() == B.GetValueOrDefault()) && (A.HasValue == B.HasValue)
And to this for more complex types:
(A.HasValue == B.HasValue) ? !A.HasValue || (A.GetValueOrDefault() == B.GetValueOrDefault())
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
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.
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;
}
I've noticed that some .NET structs can be compared to null.
For example:
TimeSpan y = new TimeSpan();
if (y == null)
return;
will compile just fine (the same with the Guid struct).
Now I know that stucts are value type and that the code above should not compile, unless there's an overload of operator == which takes an object. But, as far as I could tell there isn't.
I've looked at the class with Reflector, and also at the docs on MSDN.
The two of them do implement the following interfaces:
IComparable, IComparable<T>, IEquatable<T>
but, trying to implment the same Interfaces did not seem to help:
struct XX : IComparable, IComparable<XX>, IEquatable<XX> {
public int CompareTo(Object obj) {
return 0;
}
public int CompareTo (XX other){
return 0;
}
public bool Equals (XX other){
return false;
}
public override bool Equals(object value){
return false;
}
public static int Compare(XX t1, XX t2){
return 0;
}
}
I'm using: .NET 2.0 Visual Studio 2005.
Does anyone has any idea what's the reason for this ?
I am just trying to get a better understanding. This isn't an issue as I know I shouldn't compare structs to null anyway.
It's the == operator.
The TimeSpan class has an overload of the equality operator:
public static bool operator ==(DateTime d1, DateTime d2)
{
return (t1._ticks == t2._ticks);
}
This in itself doesn't make it possible to compare with null, but...
With the arrival of nullable types, each struct is implicitly convertible to its nullable type, so when you see something like
TimeSpan y = new TimeSpan();
if (y == null)
return;
You don't see that this is happening:
TimeSpan y = new TimeSpan();
if ((Nullable<TimeSpan>)y == (Nullable<TimeSpan>)null)
return;
Null gets the implicit conversion (implicit assignment?), but not all System.Object objects do:
TimeSpan y = new TimeSpan();
object o = null;
if (y == o) //compiler error
return;
Okay, but the equality operator doesn't take nullable arguments, does it?
Well, msdn is of help here, stating:
The predefined unary and binary
operators and any user-defined
operators that exist for value types
may also be used by nullable types.
These operators produce a null value
if [any of] the operands are null; otherwise,
the operator uses the contained value
to calculate the result.
So you effectively get a nullable implementation for each operator for free, with a fixed defined behaviour. The "contained value" mentioned above is the actual value the non-nullable operator would return.
This problem was effectively introduced when nullable types were included. There's an implicit conversion from TimeSpan to TimeSpan?, and there's a comparison between TimeSpan? and the null value of that type.
The compiler issues a warning for some types which makes it clearer what it's trying to do:
int x = 10;
if (x == null)
{
Console.WriteLine();
}
Gives this warning:
Test.cs(9,13): warning CS0472: The result of the expression is always 'false'
since a value of type 'int' is never equal to 'null' of type 'int?'
I believe Marc Gravell and I worked out the circumstances under which the warning is given once... it's a shame it's not consistent.
This case is covered for generics in section 7.9.6 of the C# language specification.
The x == null construct is permitted even though T could represent a value type, and the result is simply defined to be false when T is a value type.
I dug through the spec for a bit and couldn't find a more general rule. Jon's answer indicates it's a nullable promotion issue.
This rule (or a similar variation) does seem to be being applied here. If you look at the reflected output closely you'll notice the comparison isn't there. The C# compiler is apparently optimizing this comparison away and replacing it with false.
For instance, if you type the following
var x = new TimeSpan();
var y = x == null;
Console.WriteLine(x);
Then decompile it you'll see the following
var x = new TimeSpan();
var y = false;
Console.WriteLine(x);
See also: C# 3 (.NET 3.5) version of csc fails to report CS0162 for unrechable code (struct/null)
Starting with the C# 3 compiler that means it sometimes doesn't even warn you about this ;-p
Because Guid / TimeSpan etc provide ==, they fall into this trap where it doesn't warn you.
I FOUND IT :)
The following gives a warning:
int i = 0;
if (i == null)
// ^^ Warning: The result of the expression is always 'false' since a value of
// type 'int' is never equal to 'null' of type 'int?'
The compiler is just failing to emit the correct warning that the null you typed was converted to type TimeSpan? for the comparison.
Edit: The related section in the spec is §13.7.1 stating that null can be implicitly converted to any nullable type, and (the very difficult to read) section §13.7.2 stating a value type T can be implicitly converted to T?.
What I originally wrote:
Whatever's happening is something in the C# spec because like JaredPar says it compiles to simply false.
Note that this doesn't compile:
TimeSpan ts = new TimeSpan();
object o = null;
if (ts == o) // error, Operator '==' cannot be applied to operands of type 'System.TimeSpan' and 'object'
...