Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"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.

Showing use of == operator

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;
    }
}
  1. 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."
  2. 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.

like image 587
Timo Avatar asked Dec 03 '22 18:12

Timo


2 Answers

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:

Visual Studio screenshot

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

like image 73
canton7 Avatar answered Dec 20 '22 09:12

canton7


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.

like image 28
mjwills Avatar answered Dec 20 '22 07:12

mjwills