What are lifted operators?
Lifted operators are operators which work over nullable types by "lifting" the operators which already exist on the non-nullable form. So for example, if you do:
int? x = 10;
int? y = 10;
int? z = x + y;
That "+" operator is lifted. It doesn't actually exist on Nullable<int>
but the C# compiler acts as if it does, generating code to do the right thing. (For the most case, that's a matter of checking whether either operand is null; if so, the result is null. Otherwise, unwrap both operands to their non-nullable values, use the normal operator, and then wrap the result back into a nullable value. There are a few special cases around comparisons though.)
See section 6.4.2 (lifted conversion operators) and 7.3.7 (lifted operators) of the C# spec for more information.
Why doesn't incrementing Nullableint throw an exception?
You're observing the effects of a lifted operator.
From section 7.3.7 of the C# 5 specification:
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 unary operators
+ ++ - -- ! ~
a lifted form of an operator exists if the operand and result types are both non-nullable value types. The lifted form is constructed by adding a single?
modifier to the operand and result types. The lifted operator produces a null value if the operand is null. Otherwise, the lifted operator unwraps the operand, applies the underlying operator, and wraps the result.
So basically, a++
in this case is an expression with a result of null
(as an int?
) and the variable is left untouched.
When you call
Console.WriteLine(a);
that's being boxed into object
, which converts it to a null reference, which is printed as an empty line.
ulong == ulong? evaluated as ulong == ulong works 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 thebool
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
Is Nullableint a Predefined value type - Or how does Equals() and == work here?
In C#, there's a concept called "Lifted Operators", described in section 7.3.7 of the language specification (Version 5 download):
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
And specifically:
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.
So, since there's an ==
operator defined between int
s, there's also one defined for int?
s
Negation of null for Nullablebool
See Section 7.3.7 of the spec:
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 unary operators
+ ++ - -- ! ~
a lifted form of an operator exists if the operand and result types are both non-nullable value types. The lifted form is constructed by adding a single?
modifier to the operand and result types. The lifted operator produces a null value if the operand is null. Otherwise, the lifted operator unwraps the operand, applies the underlying operator, and wraps the result.
(Emphasis mine)
So:
bool? x = null;
bool? y = !x;
Follows this rule. We're using the lifted form of the unary !
operator, which results in null
if the value it's applied to is null
.
!null
isn't allowed, because null
isn't of type Nullable<T>
. !(bool?)null
works, however (although it produces a compiler warning).
!
indeed has higher precedence than ??
, see Section 7.3.1
Why NullableT doesn't overload == opertator?
Operators on nullable value types are actually "hardcoded" in the language spec. They are known as 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,
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.
This is specified in the language spec so the operators are all implemented by "compiler magic", which is why you don't see a declaration in the Nullable
struct.
Exactly what the compiler magic does is implementation detail, but I'm pretty sure it wouldn't do a cast to object
here, because you are using the lifted form of ==(int, int)
. According to the spec, this would apply ==(int, int)
("the underlying operator") if both operands are not null, not object.Equals
.
On sharplab.io, you can see that that version of the compiler compiles this:
public bool F(int? a, int? b) {
return a == b;
}
to:
public bool F(Nullable<int> a, Nullable<int> b)
{
Nullable<int> num = a;
Nullable<int> num2 = b;
return (num.GetValueOrDefault() == num2.GetValueOrDefault()) & (num.HasValue == num2.HasValue);
}
A generic implementation of this would be:
public static bool operator == (Nullable<T> a, Nullable<T> b) {
return (a.GetValueOrDefault() == b.GetValueOrDefault()) && (a.HasValue == b.HasValue);
}
But note that the above is not going to compile in normal C#, because ==
is not guaranteed to be defined on T
. However, since the lifted form ==(T?, T?)
exists if and only if ==(T, T)
exists, and because this is implemented by compiler magic, we're fine.
Related Topics
How to Get Values from Igrouping
Correct Way Communicate Wsse Usernametoken for Soap Webservice
Determine If Uploaded File Is Image (Any Format) on MVC
How to Determine If a File Is Binary or Text in C#
Mobile Device Detection in ASP.NET
How to Remove All White Space from the Beginning or End of a String
Fastest Way to Interface Between Live (Unsaved) Excel Data and C# Objects
How to Convert List<String> to List<Int>
Best Regular Expression for Email Validation in C#
Custom Header to Httpclient Request
How to Mock Static Methods in C# Using Moq Framework
Is There Any Simple Way to Convert .Xls File to .CSV File? (Excel)
Verifying Jwt Signed with the Rs256 Algorithm Using Public Key in C#
How to "Multiply" a String (In C#)
What Are the Downsides to Turning Off Proxycreationenabled for Ctp5 of Ef Code First