Reputation: 29548
Normally, with value (struct) types, comparison with null (or object types) will result in a compiler error.
struct Value
{
}
class Program
{
static void Main()
{
object o = new object();
Value v = default;
// Error CS0019 Operator '==' cannot be applied to operands of type 'Value' and '<null>'
var a = v == null;
// Error CS0019 Operator '==' cannot be applied to operands of type 'Value' and 'object'
var b = v == o;
}
}
However, if I add equality operator overloads on the struct, the comparison with null no longer produces a compiler error:
struct Value
{
public static bool operator ==(Value l, Value r)
{
return true;
}
public static bool operator !=(Value l, Value r)
{
return true;
}
}
class Program
{
static void Main()
{
object o = new object();
Value v = default;
// compiler is now happy with this.
var a = v == null;
// Error CS0019 Operator '==' cannot be applied to operands of type 'Value' and 'object'
var b = v == o;
}
}
I believe this has something to do with implicit conversion to Nullable<Value>
, but I can't remember the details. The question is: Is it possible to overload these operators on a struct while preserving that compiler error?
I've refactored some code, and I think there are some landmines in the codebase due to this issue. I also worry that future code might accidentally be written in this form, and I'd really like it to produce a compiler error (Without having to implement an analyzer).
Upvotes: 5
Views: 382
Reputation: 2898
This is because compiler automatically generates so called lifted operators that also work with nullable values types as you can read in docs. So for given comparison operator T, U -> bool
where T
and U
are non-nullable value types there also exist lifted operators T?, U -> bool
, T, U? -> bool
and T?, U? -> bool
.
To suppress those operators you can explicitly define them and decorate with an Obsolete
attribute with error
parameter set to true
as follows:
struct Value
{
public static bool operator ==(Value l, Value r)
{
return true;
}
public static bool operator !=(Value l, Value r)
{
return true;
}
[Obsolete("Some error message", error: true)]
public static bool operator ==(Value? l, Value r) =>
throw new NotImplementedException();
[Obsolete("Some error message", error: true)]
public static bool operator ==(Value l, Value? r) =>
throw new NotImplementedException();
[Obsolete("Some error message", error: true)]
public static bool operator ==(Value? l, Value? r) =>
throw new NotImplementedException();
[Obsolete("Some error message", error: true)]
public static bool operator !=(Value? l, Value r) =>
throw new NotImplementedException();
[Obsolete("Some error message", error: true)]
public static bool operator !=(Value l, Value? r) =>
throw new NotImplementedException();
[Obsolete("Some error message", error: true)]
public static bool operator !=(Value? l, Value? r) =>
throw new NotImplementedException();
}
Now in comparisons like new Value() == null
and also new Value() == (Value?)null
the above matching user-definied operator will be chosen because it's more specific and such error will be given:
error CS0619: 'Value.operator ==(Value, Value?)' is obsolete: 'Some error message'
Upvotes: 1