According to the rules for Binary operator overload resolution:
An operation of the form x op y
, where op
is an overloadable binary operator, x
is an expression of type X
, and y
is an expression of type Y
, is processed as follows:
- The set of candidate user-defined operators provided by
X
and Y
for the operation operator op(x,y)
is determined. The set consists of the union of the candidate operators provided by X
and the candidate operators provided by Y
, each determined using the rules of Candidate user-defined operators. [...]
Candidate user-defined operators says:
For all operator op
declarations in T0
and all lifted forms of such operators, if at least one operator is applicable with respect to the argument list A
, then the set of candidate operators consists of all such applicable operators in T0
.
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. 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.
So the compiler looks at the operands DateTime
and null
, finds the user-defined ==(DateTime, DateTime)
operator, and lifts it to ==(DateTime?, DateTime?)
. As both DateTime
and null
can be implicitly converted to DateTime?
, this lifted operator is applicable and ultimately selected as the best candidate.