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).
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'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With