V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
darktone
V2EX  ›  问与答

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

  •  
  •   darktone · 2019-10-26 20:05:58 +08:00 · 1731 次点击
    这是一个创建于 1615 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    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 值相等?

    20 条回复    2020-04-05 00:53:51 +08:00
    xgfan
        1
    xgfan  
       2019-10-26 20:26:25 +08:00 via iPhone
    自己搜索下,java 这种问题面试都被问吐了。c#估计也差不多。
    cmdOptionKana
        2
    cmdOptionKana  
       2019-10-26 20:42:00 +08:00   ❤️ 1
    一般你要比较两个对象,是不能只用 public bool Equals(Pet other)的,因为那个 other 可能不是 Pet。

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

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

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

    -
    charlie21
        4
    charlie21  
       2019-10-26 22:12:10 +08:00 via iPhone
    软件开发里有很多邪门儿的事情是你不能控制也试验不出来的 ,尤其是 标准库 / 语言内置模块 / 语言 SDK 里的东西,晓得吧 。已有崖追无涯,殆矣
    邪门儿玩意呢 往好了说叫 灵魂 ,往坏了说就是 黑魔法,作为语言研究者 你可以去研究(丰富知识),作为语言使用者 用最简单无脑的方式达到目的是赢 (节省时间)
    charlie21
        5
    charlie21  
       2019-10-26 22:12:50 +08:00 via iPhone
    *灵魂 -> 灵活
    ColinZeb
        6
    ColinZeb  
       2019-10-26 22:17:07 +08:00 via iPhone
    这是为了实现装箱比较吧,c#坑挺少的,不像 java 那样小坑大坑远古巨坑都特别多。我面试的时候见过很多面试用 java 的坑来套路面试者,很搞笑。
    darktone
        7
    darktone  
    OP
       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
        8
    darktone  
    OP
       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
        9
    cmdOptionKana  
       2019-10-26 23:01:48 +08:00
    @darktone 类型不同就应该返回 false,没必要进一步比较了。
    charlie21
        10
    charlie21  
       2019-10-26 23:04:37 +08:00   ❤️ 1
    那你需要重新考虑你的程序是否对 object 的 “类型” 那么敏感。实际上 它就是一套方法集的名字而已 ,在 OOP 的视角 叫做类,在 Procedural 的视角 叫做 module ( 根本不考虑 OOP 那一套 )
    https://en.wikipedia.org/wiki/Procedural_programming
    cmdOptionKana
        11
    cmdOptionKana  
       2019-10-26 23:10:55 +08:00
    @darktone 按照传统的 OOP 思想,你要先判断类型是否相同。charlie21 说的也是一种方法,具体看实际情况选择处理方式。

    不过刚入门还是建议先理解传统 OOP 思想,以后了解别的模式,不然一下子接受一堆思想很难消化。
    charlie21
        12
    charlie21  
       2019-10-26 23:16:23 +08:00
    它的相等和你的相等是一回事吗?你这个需求看起来实际上是想要自己定义何谓相等 ( “从逻辑上来说,a1 和 a2 的值是相等的”,这里你已经对你的 “相当” 有了明确的定义 )。那么你完全可以考虑自己定义 何谓 “相等” 并且直接去做。
    yejinmo
        13
    yejinmo  
       2019-10-26 23:48:51 +08:00
    疑惑 1:
    在比较 GDD 甚至 PDD 时,调用你重写的,按照你的相等定义去执行逻辑
    疑惑 2:
    a1 和 a2 所谓的值相等,是你定义的值相等 ( a1.Name、a1.Age 和 a2.Name、a2.Age 的值是相等的)
    如果非要比较两个不相干的类的同名同类型字段是否相等,用反射
    geelaw
        14
    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
        15
    geelaw  
       2019-10-26 23:56:38 +08:00
    关于你的第二个疑惑,一般来说我们会希望这俩不相等才对。例如一个人和一只宠物,它们都可以有名字和年龄,但是通常我们不希望它们有“相等”的概念。
    geelaw
        16
    geelaw  
       2019-10-26 23:59:49 +08:00   ❤️ 2
    @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
        17
    darktone  
    OP
       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
        18
    charlie21  
       2019-10-28 17:17:23 +08:00 via iPhone
    @darktone
    基类 object Equals(object obj) 的方法体是什么?
    geelaw
        19
    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)。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1037 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 22:29 · PVG 06:29 · LAX 15:29 · JFK 18:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.