自学者请教 关于 c#对象值相等为什么要重写 Equals(object obj)的问题,自己查了几个小时资料也没解决

2019-10-26 20:05:58 +08:00
 darktone

自己尝试写出如下代码,来判断对象相等,虽然能正常工作。

using System;

namespace 比较相等
{
    class Program
    {
        static void Main(string[] args)
        {
            

            Pet a1 = new Pet {  Name = "Turbo", Age = 8 };
            Pet a2 = new Pet { Name = "Turbo", Age = 8 };

          
       
            if (a1.Equals(a2))
                Console.WriteLine("相等");
            else
                Console.WriteLine("不相等");
           

            Console.Read();

        }
    }
    class Pet
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public  bool Equals(Pet other)
        {
            if ((Object) other ==null)
            {
                return false;
            }
            return this.Name == other.Name && this.Age == other.Age ;
        }
           

    }

}

但是查网上的资料,发现微软官方和其他博客不仅写了 Equals(Pet other),还要重写 override Equals(object obj)。

疑惑 1:

官方说实现特定的 Equals(Pet other)是为了提高性能,这个我勉强能理解。但是为什么还要重写 Equals(object obj)呢? 它的意义何在? 或者说,什么样的情况下,明明已经有了 Equals(Pet other)不会调用 Equals(Pet other),而是去调用 Equals(object obj)?

我尝试模仿官方文档,在 Pet 类中添加了 Equals(object obj),为了强行去调用 Equals(object obj)(疑惑 1 ),还单独创建了一个类 Gdd

 using System;

namespace 比较相等
{
    class Program
    {
        static void Main(string[] args)
        {
            

            Pet a1 = new Pet {  Name = "Turbo", Age = 8 };
            Gdd a2 = new Gdd { Name = "Turbo", Age = 8 };

          
       
            if (a1.Equals(a2))
                Console.WriteLine("相等");
            else
                Console.WriteLine("不相等");
           

            Console.Read();

        }
    }
    class Pet
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public  bool Equals(Pet other)
        {
            if ((Object) other ==null)
            {
                return false;
            }
            return this.Name == other.Name && this.Age == other.Age ;
        }

 public override bool Equals(object obj)
        {
           

            if (obj == null )
            {
                return false;
            }


            Pet p = obj as Pet;
            if ((Object)p == null)
            {
                return false;
            }

            return this.Name == p.Name && this.Age == p.Age;
        }
           

    }
class Gdd
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

}

疑惑 2:

从逻辑上来说,a1 和 a2 的值是相等的,此时调用的是 Equals(object obj),不过永远返回 false。

如以上代码中完全独立的两个类,怎么写 Equals 方法去判断 a1 和 a2 值相等?

1913 次点击
所在节点    问与答
20 条回复
6IbA2bj5ip3tK49j
2019-10-26 20:26:25 +08:00
自己搜索下,java 这种问题面试都被问吐了。c#估计也差不多。
cmdOptionKana
2019-10-26 20:42:00 +08:00
一般你要比较两个对象,是不能只用 public bool Equals(Pet other)的,因为那个 other 可能不是 Pet。

如果你只有 public bool Equals(Pet other),那么当你喂给他一个不是 Pet 的东西时,就会有问题了。
charlie21
2019-10-26 22:01:56 +08:00
写一个 isEqual(Pet pet, object obj) 方法 放在 Utils 类里

在方法体里 去判断 / 返回
return pet.name == obj.name && pet.age == obj.age
如果有其他需要比较多东西,就去比。完事

为什么不去重载 Pet 类 的 Equals 函数?
1. 去耦合
2. 避免你遇见的邪门儿情况

-
charlie21
2019-10-26 22:12:10 +08:00
软件开发里有很多邪门儿的事情是你不能控制也试验不出来的 ,尤其是 标准库 / 语言内置模块 / 语言 SDK 里的东西,晓得吧 。已有崖追无涯,殆矣
邪门儿玩意呢 往好了说叫 灵魂 ,往坏了说就是 黑魔法,作为语言研究者 你可以去研究(丰富知识),作为语言使用者 用最简单无脑的方式达到目的是赢 (节省时间)
charlie21
2019-10-26 22:12:50 +08:00
*灵魂 -> 灵活
ColinZeb
2019-10-26 22:17:07 +08:00
这是为了实现装箱比较吧,c#坑挺少的,不像 java 那样小坑大坑远古巨坑都特别多。我面试的时候见过很多面试用 java 的坑来套路面试者,很搞笑。
darktone
2019-10-26 22:40:13 +08:00
@cmdOptionKana 是不是说,重写 Equals(object obj)目的是为了更好的兼容性? 但是我喂一个不是 pet 的东西,比如 Gdd,就算调用了 Equals(object obj)执行到 Pet p = obj as Pet;
if ((Object)p == null)
{
return false;
这里就返回 false 了,也不会去执行 return this.Name == p.Name && this.Age == p.Age;
Equals(object obj)中这样写有啥意义呢?
darktone
2019-10-26 22:47:50 +08:00
@charlie21 多谢解答,你的建议是单独弄一个方法,而不是去重载 Equlas ?但是这里有一个问题,传进来的 object 不能保证具有 name 和 age 属性啊,如果用类型判断 GetType 或者 obj as Pet,直接就 return false 了,根本不会执行 return pet.name == obj.name && pet.age == obj.age
cmdOptionKana
2019-10-26 23:01:48 +08:00
@darktone 类型不同就应该返回 false,没必要进一步比较了。
charlie21
2019-10-26 23:04:37 +08:00
那你需要重新考虑你的程序是否对 object 的 “类型” 那么敏感。实际上 它就是一套方法集的名字而已 ,在 OOP 的视角 叫做类,在 Procedural 的视角 叫做 module ( 根本不考虑 OOP 那一套 )
https://en.wikipedia.org/wiki/Procedural_programming
cmdOptionKana
2019-10-26 23:10:55 +08:00
@darktone 按照传统的 OOP 思想,你要先判断类型是否相同。charlie21 说的也是一种方法,具体看实际情况选择处理方式。

不过刚入门还是建议先理解传统 OOP 思想,以后了解别的模式,不然一下子接受一堆思想很难消化。
charlie21
2019-10-26 23:16:23 +08:00
它的相等和你的相等是一回事吗?你这个需求看起来实际上是想要自己定义何谓相等 ( “从逻辑上来说,a1 和 a2 的值是相等的”,这里你已经对你的 “相当” 有了明确的定义 )。那么你完全可以考虑自己定义 何谓 “相等” 并且直接去做。
yejinmo
2019-10-26 23:48:51 +08:00
疑惑 1:
在比较 GDD 甚至 PDD 时,调用你重写的,按照你的相等定义去执行逻辑
疑惑 2:
a1 和 a2 所谓的值相等,是你定义的值相等 ( a1.Name、a1.Age 和 a2.Name、a2.Age 的值是相等的)
如果非要比较两个不相干的类的同名同类型字段是否相等,用反射
geelaw
2019-10-26 23:54:38 +08:00
考虑代码

(new Pet()).Equals((object)(new Pet()))

它调用的是 Pet.Equals(object other) 方法而不是 Pet.Equals(Pet other) 方法,因为重载决议是在编译时间进行的。

一个最简单的写法是

public override bool Equals(object other) { return Equals(other as Pet); }

另外 if ((Object) other ==null) 里面的类型转换是多余的。
geelaw
2019-10-26 23:56:38 +08:00
关于你的第二个疑惑,一般来说我们会希望这俩不相等才对。例如一个人和一只宠物,它们都可以有名字和年龄,但是通常我们不希望它们有“相等”的概念。
geelaw
2019-10-26 23:59:49 +08:00
@geelaw #14 另一个常见的情况是

((object)(new Pet())).Equals(new Pet())

这也会调用 Pet.Equals(object other),因为通过 object 引用的 Pet 只能访问到 object.Equals(object other) 的重写方法。

注意,这也涵盖了用接口引用访问的情形,例如

interface ISome { /* 不含有名字叫 Equals 的方法 */ }
class Pet : ISome { ... }

ISome pet = new Pet();
pet.Equals(pet); // 调用的是 Pet.Equals(object other)
darktone
2019-10-27 19:57:11 +08:00
感谢各位的回复。
又写了点测试代码,加上查资料+反汇编,我基本上弄清楚了。
仅从题目来说,特定的 Equals(Pet other)和 Equals(object obj)并非必要,两者有一个即可。

微软之所以在文档示例中两者都显式写明实现,是为了“泛用性”,“一致性”。

用户自己写的代码,当然可以显式指定去调用特定的 Equals(Pet other),但 net framework 中还有许多用户并没有显式指明的部分,此时 net framework 都会默认使用 Equals(object obj),因为它不知道用户有没有实现特定的 Equals(Pet other),自然不能预先假设,但 net 有一点可以确定,哪怕用户没有自定义的 overwrite Equals(object obj),也有基类 object Equals(object obj)可用,不会产生异常。
charlie21
2019-10-28 17:17:23 +08:00
@darktone
基类 object Equals(object obj) 的方法体是什么?
geelaw
2019-10-30 04:22:07 +08:00
@darktone #17 这个理解是完全没有搞清楚。你这样写会导致很多 implicit invariant 失效,从而程序虽然可以运行,但是意思却不是你想的那样,虽然没有产生 exception,但是几乎一定是错误的程序。
通常我们希望 object.Equals(object other) 的重写方法是判断对象相等性,如果你不重写,对象的相等性会被理解为同一性。

另外“(方法)重写”是 override 而不是 overwrite。

@charlie21 #18 object.Equals(object other) 的实现是 object.ReferenceEquals(this, other)。
charlie21
2020-04-05 00:53:51 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/613275

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX