当一个类有多个==运算符重载时,对空值进行检查是模棱两可的。

16
我有一个类,其中包含两个重载的==运算符,用于将其与该类的其他实例进行比较,以及将其与字符串实例进行比较。
class SomeClass
{
    string value;
    public SomeClass (string _Value)
    {
        value = _Value;
    }

    static public bool operator == (SomeClass C1, SomeClass C2)
    {
        return C1.value == C2.value;
    }

    static public bool operator != (SomeClass C1, SomeClass C2)
    {
        return C1.value != C2.value;
    }

    static public bool operator == (SomeClass C1, string C2)
    {
        return C1.value == (string) C2;
    }

    static public bool operator != (SomeClass C1, string C2)
    {
        return C1.value != (string) C2;
    }
}

然而,当我尝试将这个类与 null 进行比较时:

        Console.WriteLine(someObject == null);

我遇到了以下错误:
Error CS0121: The call is ambiguous between the following methods or properties: `SomeClass.operator ==(SomeClass, SomeClass)' and `SomeClass.operator ==(SomeClass, string)'

如何定义我的==重载,以便我仍然可以对此类的实例进行空检查?


1
也许你可以用null coalesce做一些聪明的事情。 - tnw
4个回答

15

由于您使用了null字面量,编译器不知道该调用哪个方法,因为stringSomeClass都可以是空值。

一种强制编译器选择其中一个方法的技术是对空值进行类型转换。

Console.WriteLine(someObject == ((SomeClass)null));

更好的方法是,不要直接使用null,而是使用default关键字来获取空值(因为当T是引用类型时,default(T)等同于null)。

Console.WriteLine(someObject == default(SomeClass));

那被证明是最优雅的方式,谢谢。虽然在代码中(SomeClass)null看起来很奇怪(直到今天我也会感到惊讶),但我认为它对可读性仍然非常好:如果维护者决定探究它,他只需将其删除以查看错误,并且(应该)可以轻松理解这个“咒语”的目的。 - Max Yankov
2
@golergka,现在,根据你在问题中展示的实际实现,你重载的 == 可能都会引发 NullReferenceException。也许你想要使用 C# 内置的重载运算符 operator ==(object, object)(实际上在 IL 中不是方法)?我认为你是这么想的。那么可以使用 someObject == (object)null,或者(object)someObject == null,或者(object)someObject == (object)null(这三个都将进入内置的 ==,而不是你的任何操作符)。 - Jeppe Stig Nielsen

11

不需要定义两个等号运算符,你可以在 stringSomeClass 之间创建一个隐式转换:

class SomeClass
{
    string value;
    public SomeClass(string _Value)
    {
        value = _Value;
    }
    static public bool operator ==(SomeClass C1, SomeClass C2)
    {
        return C1.value == C2.value;
    }
    static public bool operator !=(SomeClass C1, SomeClass C2)
    {
        return C1.value != C2.value;
    }

    public static implicit operator string(SomeClass instance)
    {
        return instance.value;
    }

    public static implicit operator SomeClass(string str)
    {
        return new SomeClass(str);
    }
    //TODO override Equals and GetHashCode to use `value`
}

现在当您将值与null进行比较时,就不会有歧义问题了。

这也会带来一个副作用:使得这些类在其他地方隐式地相互转换,不过根据上下文来看,似乎这并不是什么坏事。


1
这是个好主意。然而,在这种特殊情况下,该类的目标(显然不是问题中提出的那个)是替换字符串类,以便提供类似的功能和额外的约束,并确保在某些情况下不使用字符串——因此隐式转换会破坏它的目的。然而,总的来说,这是一个很好的解决方案,谢谢。 - Max Yankov
someObject == null不再含糊(也不会经过implicit operator),但是当然operator ==的实现会导致确定的NullReferenceException,因为C2将是null - Jeppe Stig Nielsen

1

如果你来晚了,请参考下面由@Jeppe Stig Nielsen在评论中提供的更可接受的答案。

OP特别询问了覆盖operator ==,然而,我认为这是覆盖==操作符时重要的一部分信息,并且相信正确的答案应该是:

Console.WriteLine((object)someObject == null);

使用被接受的答案并在您的对象中实现“==”和“Equals”,您将继续获得相同的错误。最好在最低级别对象上与null进行比较,这样您就可以将“object”与null进行比较,并且所有歧义都从比较中消除。

以下是根据MSDN实施的原因和解决方案:重写Equals()和Operator ==的准则

请考虑以下内容,请参考Equals实现中的注释:

class SomeClass
{
    string value;
    public SomeClass(string _Value)
    {
        value = _Value;
    }

    static public bool operator ==(SomeClass C1, SomeClass C2)
    {
        return C1.value == C2.value;
    }

    public override bool Equals(SomeClass C1)
    {
        // causes error due to unsure which operator == to use the SomeClass == or the object ==
        // Actual error: Operator '==' is ambiguous on operands of type 'SomeClass' and '<null>'
        if (C1 == null)
            return false;

        // Give same error as above
        if (C1 == default(SomeClass))
            return false;

        // Removes ambiguity and compares using base objects == to null
        if ((object)C1 == null)
            return false;

        return value == C1.value;
    }
}

1
您可以将第二个参数传递为“对象”,并在决定要执行哪种相等性之前检查其类型。
static public bool operator == (SomeClass C1, object C2)
{
  if(C2 is SomeClass)
    return C1.value == ((SomeClass)C2).value;
  else if (C2 is string)
    return C1.value == (string) C2;
}

但是你也可以传递一个布尔值,而那甚至不应该编译。 - Servy
代码显然是部分的,因为它需要另一个检查来处理不支持的类型,而我不知道 OP 想如何处理。 - Konstantin
无论如何,这只是一个运行时检查,而不是编译时检查。它应该是一个编译时检查。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接