在C#中重载的==运算符中,操作数的顺序是否重要?

4

我是一名第三方工具(Unity3d)的用户,其中一个基本的基类重载了==运算符(UnityEngine.Object)。

重载运算符的签名为:

public static bool operator == (UnityEngine.Object x, UnityEngine.Object y)

在使用重载运算符时,比较的顺序是否会对其使用产生影响?

举个例子,以下两个语句是否都会使用重载运算符并返回相同的结果?

// initialize with some value
UnityEngine.Object a = .... 

Debug.Log(a == null);
Debug.Log(null == a);

我问这个问题的原因是想要(有时)避免重载行为(使用默认的==操作符),我在想是否交换顺序会有所帮助?
(还有另一种选择 - 将操作数转换为System.object,但我不确定是否有效)。
3个回答

3

如果操作符被糟糕地重载了,那么这两个调用可能不会相同。或者有一个重载,并且它的写法是不对称比较操作数,或者这两个语句调用不同的重载。

但是假设已经正确地重载了操作符,那么这应该是绝对没问题的。当然,前提是你想调用重载的操作符。在你不想调用它的情况下,我会使用ReferenceEquals来清晰地表明。

我个人推荐使用if (a == null)的方式,因为我发现它更易于阅读(我相信许多人也是如此)。而“尤达”风格的if (null == a)有时被C程序员使用,他们担心打错字,其中if (a = null)将是一种赋值和有效的语句...虽然在良好的C编译器中会有警告。

以下是一个实现不好的重载示例,其中操作数的顺序很重要,因为null可以转换为stringTest

using System;

class Test
{
    public static bool operator ==(Test t, string x)
    {
        Console.WriteLine("Test == string");
        return false;
    }

    public static bool operator !=(Test t, string x)
    {
        Console.WriteLine("Test != string");
        return false;
    }

    public static bool operator ==(string x, Test t)
    {
        Console.WriteLine("string == Test");
        return false;
    }

    public static bool operator !=(string x, Test t)
    {
        Console.WriteLine("string != Test");
        return false;
    }

    static void Main(string[] args)
    {
        Test t = null;
        Console.WriteLine(t == null);
        Console.WriteLine(null == t);
    }
}

现在我们看到问题已经更新,所以不是那种情况...但是实现可能仍然很差。例如,它可以写成:

public static bool operator == (UnityEngine.Object x, UnityEngine.Object y)
{
    // Awful implementation - do not use!
    return x.Equals(y);
}

在这种情况下,如果x为null,则会出现NullReferenceException错误,但如果x不为null但y为null(假设Equals已正确编写),则会成功。尽管如此,我仍然期望操作符已经被正确编写,这不应该是一个问题。

@zmbq:我现在只是在测试...(两个操作数可以有不同的类型,但目前在调用时会出现错误。) - Jon Skeet
@zmbq:我现在明白了 - 它肯定可以不同,尽管它很糟糕。我会添加一个例子。 - Jon Skeet
这个链接意味着操作数的顺序不影响重载解析:https://msdn.microsoft.com/en-us/library/aa691326%28v=vs.71%29.aspx。尽管没有明确说明。 - zmbq
1
@zmbq: 我并不认为它暗示了这一点,特别是有“按照参数列表(x, y)选择最佳操作符的候选操作符集合应用7.4.2节的重载解析规则”这句话。请看我更新后的答案,其中有一个例子说明它可以影响到它。 - Jon Skeet
1
@lysergic-acid:是的,通常我会使用ReferenceEquals而不是转换。我没有注意到你更新的那部分 - 请尽可能从一开始就问清楚所有问题。如果一个问题发展得很快,回答者很难跟上,导致页面混乱。 - Jon Skeet
显示剩余10条评论

2

在更新问题后进行更新:

如果您想避免在与 null 进行比较时调用重载函数,请使用 ReferenceEquals

if(a.ReferenceEquals(null)) ...

旧的答案已经被@Jon Skeet宣布为过时并且错误...

根据这个链接,使用X和Y作为参数的二元运算符的重载解析是通过首先取X和Y定义的所有运算符的并集来完成的。

因此,a==b的结果与b==a完全相同。

该链接来自于2003年,但我怀疑Microsoft自那以后是否有进行更改,否则将会破坏许多旧代码。


谢谢。我的建议也可以吗?例如:将对象转换为Object类型:if ((System.Object)obj == null) - lysergic-acid
Unity游戏引擎不使用微软代码,它依赖于一个*旧版本的Mono,因此在那里理论上可能会有所不同或出现故障。 - Max Yankov

1
更新:虽然我在之前的Unity版本中见过这种行为,但是我无法在我写完这个答案后进行的测试中复现它。也许Unity改变了==运算符的行为,或者改变了UnityEngine.Objectbool的隐式转换行为;然而,我仍然建议使用重载的==运算符,而不是试图避免它。
特别是Unity覆盖==运算符的方式非常令人沮丧,但也是你必须与引擎一起工作的基础。当UnityObject被销毁时,通过调用Destroy(在调用后的某个时间)或DestroyImmediate(在调用后立即进行,如名称所示),将此对象与null进行比较会返回true,尽管它不是空引用。
对于C#程序员来说,这完全不直观,可能会在你找出问题之前产生很多令人疑惑的时刻。例如:
DestroyImmediately(someObject);
if (someObject)
{
    Debug.Log("This gets printed");
}
if (someObject != null)
{
    Debug.Log("This doesn't");
}

我解释的原因是因为我完全理解您想要避免这种奇怪的覆盖行为的愿望,特别是在使用Unity之前有C#经验的情况下。但是,与许多其他特定于Unity的内容一样,实际上最好只坚持Unity约定而不是尝试以C#正确的方式实现所有内容。

我非常清楚这种转换以及Unity使用的null引用与null“包装对象”的古怪行为。实际上,我试图避免的问题是从不同的线程访问对象。我们有一个单例检查其实例是否为空(该实例是MonoBehaviour)。从另一个线程访问它会抛出异常(无法从不同的线程检查instance == null)。在这种情况下,我对普通的引用检查感兴趣。顺便说一句,我被告知这种行为在Unity 5中已更改。 - lysergic-acid
1
@lysergic-acid 对于我显而易见的问题,我感到很抱歉——90%的人在unity3d标签上提问都是初学者,我习惯于过度解释一切。但是根据我的经验,在不同的线程中处理UnityEngine.Object实例是不现实的,因此我只使用它们来处理完全不使用任何Unity API的抽象工作。如果您可以成功地使用其他线程来处理Unity对象,请写更多关于它的内容。 - Max Yankov
在除了脚本线程以外的其他线程上执行操作确实几乎是不可能的。然而,在Unity 5中,==运算符可以在其他线程上工作(尽管我还没有尝试过)。 - lysergic-acid

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