操作数的顺序对于 C# 中重载的 == 运算符是不是重要

Posted

技术标签:

【中文标题】操作数的顺序对于 C# 中重载的 == 运算符是不是重要【英文标题】:Does the order of operands matter for an overloaded == operator in C#操作数的顺序对于 C# 中重载的 == 运算符是否重要 【发布时间】:2015-05-10 06:44:42 【问题描述】:

我正在使用第 3 方工具 (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,但我不能 100% 确定可行)。

【问题讨论】:

【参考方案1】:

好吧,如果操作员严重过载,这两个调用可能会不一样。可能存在一个重载,并且可以以不对称比较操作数的方式编写,或者两个语句可以调用不同的重载。

但假设它已经适当地重载,那应该是绝对没问题的。当然,如果你想要调用重载的操作符的话。在你不知道的情况下,我会使用ReferenceEquals 说明这一点。

我个人会推荐if (a == null) 方法,因为我发现它更容易阅读(我相信许多其他人也是如此)。 if (null == a) 的“yoda”风格有时被害怕拼写错误的 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 为空时,它将失败并返回NullReferenceException,但如果x 为非空但y 为空(假设Equals 已正确写入)则成功。

不过,我希望运算符写得正确,这不是问题。

【讨论】:

这两个语句如何导致两个不同的重载解决方案? @zmbq:我现在只是在测试...(这两个操作数可能有不同的类型,但目前调用时会出错。) @zmbq:我现在明白了——它肯定会有所不同,尽管它很可怕。将添加一个示例。 @zmbq:我不认为这意味着,特别是“第 7.4.2 节的重载解决规则应用于候选运算符集以选择最佳运算符到参数列表 (x, y)”,请参阅我更新的答案以了解它可以影响它的位置。 @lysergic-acid:是的,我一般会使用ReferenceEquals 而不是强制转换。我没有发现你更新的那部分 - 如果可能的话,请注意从一开始就问你所有的问题。如果一个问题发生了很大变化,那么回答者就很难跟上,从而导致页面普遍混乱。【参考方案2】:

更新问题后更新:

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

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

较早的答案,已过时并且@Jon Skeet 也错误...

根据this,以 X 和 Y 作为参数的二元运算符的重载解析是通过首先将所有由 X 和 Y 定义的运算符的并集来完成的。

所以a==b 导致与b==a 完全相同的重载分辨率。

该链接来自 2003 年,但我怀疑微软已经改变了一些东西,因为它会破坏很多旧代码。

【讨论】:

谢谢。我的建议也有效吗?例如:转换为对象:if ( (System.Object)obj == null) Unity 游戏引擎不使用微软代码;它依赖于 Mono 的 old 版本,因此理论上它可能会有所不同或损坏。【参考方案3】:

更新:虽然我在 Unity 的早期版本中目睹了这种行为,但我无法在写完此答案后立即执行的测试中重现它。可能是 Unity 更改了 == 运算符的行为,或者将 UnityEngine.Object 隐式转换为 bool 的行为;但是,我仍然提倡使用重写的 == 运算符,而不是试图避免它。

Unity 覆盖 == 运算符的方式尤其令人沮丧,但对于如何使用引擎来说也是必不可少的。在 UnityObject 被销毁后,通过调用 Destroy(在调用之后的某个时间)或 DestroyImmediate(在调用之后,顾名思义),将此对象与 null 的比较返回 true,即使它不是空引用。

这对于 C# 程序员来说是完全不直观的,并且可以在你弄明白之前创造很多真正的 WTF 时刻。以这个为例:

DestroyImmediately(someObject);
if (someObject)

    Debug.Log("This gets printed");

if (someObject != null)

    Debug.Log("This doesn't");

我之所以这么解释,是因为我完全理解您希望避免这种奇怪的被覆盖行为的愿望,特别是如果您在使用 Unity 之前有 C# 经验。但是,与许多其他特定于 Unity 的东西一样,坚持 Unity 约定实际上可能更好,而不是尝试以 C# 正确的方式实现所有内容。

【讨论】:

我实际上很清楚这种转换以及空引用与 Unity 使用的空“包装器对象”的古怪之处。在这种情况下,我实际上试图避免的问题是从不同的线程访问对象。我们有一个单例来检查它的实例是否为空(该实例是 MonoBehaviour)。从另一个线程访问它会引发异常(无法检查 instance == null 是否来自不同的线程)。在这种情况下,我对普通的旧参考检查感兴趣。顺便说一句,有人告诉我这种行为在 Unity 5 中发生了变化。 @lysergic-acid 我很抱歉当时很明显是队长——90% 在 unity3d 标签上提问的人都是初学者,我养成了过度解释一切的习惯。但是根据我的经验,在不同线程中对 UnityEngine.Object 实例做任何事情是不现实的,所以我只将它们用于根本不使用任何 Unity API 的抽象工作。如果你可以使用其他线程成功处理 Unity 的对象,你能写更多关于它的内容吗? 确实几乎不可能在脚本线程之外的任何东西上做任何事情。然而,在 Unity 5 中,== 运算符适用于其他线程(虽然还没有尝试过)。

以上是关于操作数的顺序对于 C# 中重载的 == 运算符是不是重要的主要内容,如果未能解决你的问题,请参考以下文章

C#中的==EqualReferenceEqual

C#中的==EqualReferenceEqual

C#运算符重载---逐步地分析与理解

重载逗号运算符*真的*会影响其操作数的评估顺序吗?

一起学习《C#高级编程》3--运算符重载

(45)C#里使用操作符重载来提高阅读性和性能