Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to suppress implicit conversion from null on a struct with operator overloads?

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

like image 548
MarkPflug Avatar asked Nov 07 '22 06:11

MarkPflug


1 Answers

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'

like image 127
arekzyla Avatar answered Nov 15 '22 00:11

arekzyla