是否可以在具有运算符重载的结构上抑制从 null 的隐式转换?

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

通常,对于值(结构)类型,与 null(或对象类型)进行比较会导致编译器错误。

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;
    }
}

但是,如果我在结构上添加相等运算符重载,与 null 的比较将不再产生编译器错误:

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;
    }
}

我相信这与向 Nullable<Value> 的隐式转换有关,但我记不起细节了。问题是:是否可以在保留编译器错误的同时在结构上重载这些运算符?

我重构了一些代码,我认为由于这个问题,代码库中有一些地雷。我也担心以后的代码可能会不小心写成这种形式,我真的很希望它产生一个编译器错误(而不必实现分析器)。

这是因为编译器会自动生成所谓的提升运算符,这些运算符也适用于可空值类型,您可以在 docs 中阅读。因此,对于给定的比较运算符 T, U -> bool,其中 TU 是不可为 null 的值类型,还存在提升运算符 T?, U -> boolT, U? -> boolT?, U? -> bool .

要抑制这些运算符,您可以显式定义它们并使用 Obsolete 属性进行修饰,并将 error 参数设置为 true,如下所示:

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();
}

现在在像 new Value() == nullnew Value() == (Value?)null 这样的比较中,将选择上面匹配的用户定义的运算符,因为它更具体并且会给出这样的错误:

error CS0619: 'Value.operator ==(Value, Value?)' is obsolete: 'Some error message'