• .NET对象判等归纳与总结


    1、引言

      最近在看《CLR via C#》看到对象判等的那一节,觉得这也是.NET基础知识中比较重要的部分就写一篇博文来简单的总结归纳一下。

    2、.NET下的对象判等

      在.NET中关于对象判等有四个方法。依次是:System.Object.ReferenceEquals、System.Object.Equals、Instance.Equals(实例方法)、Operator==操作符。下面我们就来比较下这四个方法的异同点。

      我们打开ILSpy反编译软件,看一下System.Object基类中关于对象判等的方法。如图所示:

      2.1 System.ReferenceEquals方法

      我们看到System.Object.ReferenceEquals方法比较的是两个引用对象的内存地址。即:如果两个引用类型的指针都指向堆上面的同一个对象,那么返回True,否则返回False。注意:对于两个值类型调用该方法会永远返回False,因为值类型在转化为Object类型的时候需要进行装箱(在堆上分配对象),两个值类型的对象会在堆上面分配两个对象。所以永远返回False

      2.2 Operator ==操作符

      ==操作符在.NET中根据值类型和引用类型的的不同会有不同的默认行为。当比较的双方是引用类型时,==操作符默认比较的是两个对象的内存地址。即与System.Object.Refernece方法一致。当比较的双方是值类型(编译器内置的基元类型时)是比较值是否相等。

      2.3 System.Object.Equals方法

      通过上面的源码我们看到这个方法主要依赖于==操作符和Equals的实例方法。如果objA,objB两个对象指向同一个内存对象那么就返回True。否则的话比较结果就会依赖于Instance.Equals方法。

      2.4 Instance.Equals方法

      我们通过上面的源码分析,可以看到return RuntimeHelpers.Equals(this, obj);这一句代码。其实在内部System.Object提供的这个Equals实例方法的逻辑很简单。就是比较obj==this。显然这个对于我们自定义的类型比较是不合理的。这个方法是virtual的,我们可以重写这个方法来自定义我们的比较规则。下面来看一下具体的重写规则。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace CryptUntility
    {
        class BaseClass
        {
            public int baseFieldInt;
            public string baseFiledString;
            private bool flag;
    
            public BaseClass(int filedInt, string fieldString,bool flag)
            {
                this.baseFieldInt = filedInt;
                this.baseFiledString = fieldString;
                this.flag = flag;
            }
    
            public override bool Equals(object obj)
            {
                //如果obj为null肯定不相等,因为调用该方法的this不可能是null,否则会出现异常
                if (obj == null)
                {
                    return false;
                }
                //如果引用的是同一个对象,肯定相等
                if (ReferenceEquals(this, obj))
                {
                    return true;
                }
                //是否能够转型到DevideClass,如果不能说明不是同一类型的,肯定不相等
                BaseClass baseClass = obj as BaseClass;
                if (baseClass == null)
                {
                    return false;
                }
                //接下来依次比较各个值是否相等
                if (FiledEqule(this, baseClass))
                {
                    return true;
                }
                return false;
            }
    
            private bool FiledEqule(BaseClass obj1, BaseClass obj2)
            {
                if (obj1.baseFieldInt == obj2.baseFieldInt
                    && obj1.baseFiledString.Equals(obj2.baseFiledString)
                    && obj1.flag == obj2.flag)
                {
                    return true;
                }
                return false;
            }
        }
    
        class DevideClass : BaseClass
        {
            public int devideFidldInt;
            public string devideFieldString;
    
            public DevideClass(int filedInt, string fieldString, bool flag)
                : base(filedInt, fieldString, flag)
            {
                this.devideFidldInt = filedInt;
                this.devideFieldString = fieldString;
            }
    
            public override string ToString()
            {
                return String.Format("{0},{1}", devideFidldInt.ToString(), devideFieldString);
            }
    
            /// <summary>
            /// 重写比较规则
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            public override bool Equals(object obj)
            {
                //如果obj为null肯定不相等,因为调用该方法的this不可能是null,否则会出现异常
                if (obj == null)
                {
                    return false;
                }
                //如果引用的是同一个对象,肯定相等
                if(ReferenceEquals(this,obj))
                {
                    return true;
                }
                //是否能够转型到DevideClass,如果不能说明不是同一类型的,肯定不相等
                DevideClass devideObj = obj as DevideClass;
                if (devideObj == null)
                {
                    return false;
                }
                //接下来依次比较各个值是否相等
                if (FiledEqule(this, devideObj))
                {
                    /**
                     * 如果当前类是从System.Object类继承的那么不需要调用base方法
                     * 否则需要调用base(基类方法)来比较基类型的字段
                     * 因为有些基类字段有可能是私有的,需要将其提交到基类进行比较
                     * */
                    return base.Equals(devideObj);
                }
                return false;
            }
    
    
            private bool FiledEqule(DevideClass obj1, DevideClass obj2)
            {
                if (obj1.devideFidldInt == obj2.devideFidldInt
                    && obj1.devideFieldString.Equals(obj2.devideFieldString)
                    && obj1.baseFieldInt == obj2.baseFieldInt
                    && obj1.baseFiledString.Equals(obj2.baseFiledString))
                {
    
                    return true;
                }
                return false;
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                DevideClass dev1 = new DevideClass(120, "GG", true);
                DevideClass dev2 = new DevideClass(120, "GG", false);
    
                Console.WriteLine("dev1==dev2:"+dev1.Equals(dev2));
    
                Console.Read();
            }
        }
    }
    

      在以上的代码中我们看到我们在重写的时候具体的流程如下:

      1、第一步判断参数obj是否为null。作为实例方法那么this对象本身肯定不能是null,否则的话爆出异常的。如果obj==null那么肯定不相等。

      2、使用ReferenceEquals方法来判断两个对象是否指向同一个内存对象。这是加快对象判断的一个方法。

      3、接下来我们可以尝试着对需要比较的对象进行转型,如果不能转型成功,那么与this对象不是同一类型的对象。类型不同肯定不会相等。

      4、接下来我们可以依次比较我们自定义对象的各个字段,使用系统内置的Equals来进行比较。注意:如果一个类型的上一级基类不是System.Object类型,那么在进行字段比较的时候需要调用基类的方法进行比较。因为基类中可能有些private字段是需要通过基类的Equals方法来来进行验证的。

    3、最后的注意点

      我们重新Equals方法的同时也必须重新GetHashCode方法。相关内容我有机会会写出了和大家分享。大家也可以网上查阅相关的知识点。

  • 相关阅读:
    python线程的条件变量Condition的用法实例
    Django使用本地css/js文件的基本流程
    html添加css样式的两种方法
    Atom安装插件的几种方式
    Atom 编辑器实时预览 HTML 页面经典方法
    在Mac/linux上查找(并终止)进程锁定特定端口的几种方法
    Django使用Bootstrap的经典方法
    一个研发出身创业者的2019年收获与感悟
    百度DMA+小度App的蓝牙语音解决方案展示
    一个芯片小贩的2019年终总结
  • 原文地址:https://www.cnblogs.com/dreamGong/p/4588265.html
Copyright © 2020-2023  润新知