为啥 == 在比较具有相同 int 值的两个对象类型变量时不起作用

Posted

技术标签:

【中文标题】为啥 == 在比较具有相同 int 值的两个对象类型变量时不起作用【英文标题】:Why does == not work while comparing two object type variables boxed with same int value为什么 == 在比较具有相同 int 值的两个对象类型变量时不起作用 【发布时间】:2016-03-31 06:47:13 【问题描述】:

尝试在 C# 中实现一个简单的单链表时,我注意到 == 在比较两个用 int 值装箱的对象类型变量时不起作用,但 .Equals 起作用。

想看看为什么会这样。

下面的sn-p是一个通用的对象类型Data属性

public class Node 
    /// <summary>
    /// Data contained in the node
    /// </summary>
    private object Data  get; set; ;

下面的代码遍历单链表,搜索object类型的值-

/// <summary>
/// <param name="d">Data to be searched in all the nodes of a singly linked list
/// Traverses through each node of a singly linked list and searches for an element
/// <returns>Node if the searched element exists else null </returns>
public Node Search(object d)

    Node temp = head;

    while (temp != null)
    
        if (temp.Data.Equals(d))
        
            return temp;
        

        temp = temp.Next;
    

    return null;

但是,如果我替换

temp.Data.Equals(d)

与 temp.Data == d

即使temp.Datad 的值都为“3”,它也会停止工作。 == 不适用于对象类型变量的任何原因?

这是 Main 函数的 sn-p -

SinglyLinkedList list = new SinglyLinkedList();
list.Insert(1);
list.Insert(2);
list.Insert(3);
list.Insert(4);
list.Insert(5);

list.Print();

Node mid = list.Search(3);

我相信,由于我传递了一个 int 值 3 并且 Search 方法需要一个对象类型,因此它会成功地将 3 装箱为对象类型。但是,不知道为什么 == 不起作用,而 .Equals 起作用。

== 运算符是否仅针对值类型重载?

【问题讨论】:

你应该使用泛型。 是的,这只是为了练习目的。我意识到泛型已经有一个 LinkedList 实现 【参考方案1】:

这是因为==System.Object 实现测试引用相等,就像静态Equals(object, object),而实例Equals(object) 被重载,所以它检查实际值。

当你对一个值类型进行两次装箱时,你会得到两个不同的实例,所以引用相等当然会失败。

运算符是静态的,在编译时绑定,因此没有动态调度。即使字符串已经是引用类型,因此在分配给对象类型变量时不会被装箱,如果其中一个操作数的静态类型不是string,您可以使用 == 运算符获得意外的引用比较。

【讨论】:

谢谢。是的,== 所做的引用相等检查是无意的。将来我也会为字符串记住这一点。【参考方案2】:

有两个原因:

Equals 不受== 的限制,反之亦然,默认情况下会检查引用相等性:

正如你在.Equals vs ==的规范中看到的那样:

默认情况下,运算符== 通过确定两个引用是否指示同一个对象来测试引用相等性,因此引用类型不需要实现运算符== 来获得此功能。当一个类型是不可变的,这意味着实例中包含的数据不能改变,重载运算符== 来比较值相等而不是引用相等可能很有用,因为作为不可变对象,它们可以被认为是相同的,只要它们有相同的值。不建议在非不可变类型中覆盖运算符 ==

重载运算符== 实现不应抛出异常。任何重载运算符== 的类型也应该重载运算符!=

虽然如果你不覆盖!=,编译器会抛出一个错误,并且会警告你最好覆盖.Equals.GetHashCode

所以覆盖/重载.Equals==!= 是不同的事情。覆盖.Equals 对重载==!= 没有影响。毕竟== 是一个自定义运算符。尽管这样做并不明智,但您可以将其用于其他目的而不是相等性检查。

更多运算符在编译时解析:

下面以csharp交互式shell程序为例:

$ csharp
Mono C# Shell, type "help;" for help

Enter statements below.
csharp> public class Foo 
      >  
      > public int data;
      >  
      > public static bool operator == (Foo f1, Foo f2) 
      >     return f1.data == f2.data;
      > 
      >  
      > public static bool operator != (Foo f1, Foo f2) 
      >  
      >     return f1.data != f2.data;
      > 
      >  
      > 
(1,15): warning CS0660: `Foo' defines operator == or operator != but does not override Object.Equals(object o)
(1,15): warning CS0661: `Foo' defines operator == or operator != but does not override Object.GetHashCode()
csharp> object f = new Foo();
csharp> object f2 = new Foo();
csharp> f == f2
false
csharp> Foo f3 = f as Foo;
csharp> Foo f4 = f2 as Foo;
csharp> f3 == f4
true

如您所见,如果您将对象称为objectFoo,则== 会给出不同的结果。由于您使用 object,因此 C# 在编译时唯一可以进行的绑定是具有引用相等性的绑定。

【讨论】:

很酷的解释。感谢您引用有关如何覆盖 == 和 != 的示例来调整引用相等性以比较值。 @Ankit 不,您不能覆盖运算符。你只能超载它们。请注意,int 当然会超载==。这里的关键是静态(编译时)类型,这很重要,因为这是重载而不是覆盖。 感谢您的评论@phoog。阅读您的评论和link 让我明白,运算符确实在编译时被重载和绑定,而不是因为它们是静态的而被覆盖。该链接确实指出了运算符覆盖的解决方法 - 在重载运算符中使用虚拟方法,然后可以覆盖虚拟方法:) @Ankit 但该技术的用途有限。特别是,如果调用 == 的 System.Object 重载,那么在您自己的类的该运算符的重载中是否使用了虚拟方法或反射或魔法粉红色小马都无关紧要,因为该重载永远不会得到调用。 对@phoog。我不喜欢 == 运算符的功能受到 System.Object 的限制,一方面它们的重载在编译时被静态绑定,另一方面,不可能覆盖它。没有阅读太多关于 C# 4.0 中引入的 dynamic 关键字的信息。我怀疑它是否允许将 System.Object 重载的 == 动态绑定到自定义值相等检查而不是引用相等。将尝试阅读更多内容。【参考方案3】:

运算符== 就像一个根据编译时类型选择的重载静态函数。在您的情况下,值的类型是 Object== 运算符为此实现了引用相等。

另一方面,.Equals 是虚拟的并被覆盖,因此它会根据实际类型进行比较。

【讨论】:

对。感谢@hege_hegedus 的问题编辑,让我知道== 实现引用相等与.Equals 是虚拟的和被覆盖的,因此比较实际类型。

以上是关于为啥 == 在比较具有相同 int 值的两个对象类型变量时不起作用的主要内容,如果未能解决你的问题,请参考以下文章

为啥java 里要判断两个对象是不是相等呢?

JAVA中,为啥object对象中的equals方法比较的是同一,而String对象比较的是相等?

比较两个字典是不是相等

Java 为啥不提供decimal

如何将具有不同值的相同 JSON 对象反序列化为 java 类

python 中关于字典的键