一、重载和重写的调用区别
==是方法重载(参数个数或参数类型不同),在实际调用中会根据引用类型(不是实际对象)来调用,如Father f = new Son() 通过f会调用到 ==(Father, Father)重载,而不是==(Son, Son)重载
Equals是方法重写,在实际调用中会根据实际对象(不是引用类型)来调用,如Father f = new Son() 通过f会调用到 Son.Equals(object)方法,而不是Father.Equals(object)方法
二、所有类型基类object和值类型基类ValueType
首先对于基类object,它的==是进行同一性的比较,即比较是否是同一个对象,而在其Equals的实现中,也是直接调用的==,所以对于object,它的==和equals都是进行的同一性的比较
在值类型基类ValueType,虽然是继承自object,但是它有自己的==重载以及重写了Equals方法,使其进行相等性比较,即比较内容是否一致,但它的Equals重写是通过反射实现的,速度较慢,所以当我们自定义其他值类型(如struct)时,强烈建议我们重写自己的Equals方法,而不是用基类ValueType的
PS:为什么要用反射?因为ValueType不知道我们的值类型到底有哪些字段,所以需要通过反射将所有字段取出来然后逐一进行相等性比较
三、值类型与引用类型
对于值类型,我们需要自己手动重载自己的==,如果我们不重载而直接使用==,会编译不通过,例如float、double实现中都有重载自己的==的,重载后的==是进行相等性比较,而Equals方法由于继承了ValueType的Equals方法,也是进行的相等性比较,所以我们可以不重写(但一般都会重写,原因前面已经提到了=。=),例如float、double实现中也是有重写自己的Equals方法的,同时还会定义一个参数为当前类型的Equals方法,如Double类中public bool Equals(Double obj) 和public override bool Equals(object obj)
对于引用类型string,虽然它是引用类型,但是因为它重载了自己的==,和重写了自己的Equals,所以它的Equals和==都是相等性比较
对于其他引用类型,比如我们自定义的类,因为都是隐式继承自object的,在没有重写Equals和重载==的情况下都是和object一样进行的同一性比较,但一般涉及比较接口时,我们都会在自定义的引用类型中重写我们自己的Equals和重载==
特殊的,比如一个Int或者string类型赋值给object,这时去比较==和Equals是什么情况呢?
String s1 = new String("aa"); String s2 = new String("aa"); string s3 = "bb"; string s4 = "bb"; int i1 = 1; int i2 = 1; object o1 = s1; object o2 = s2; object o3 = s3; object o4 = s4; object o5 = i1; object o6 = i2; Console.WriteLine(o1==o2) //False Console.WriteLine(o1.Equals(o2)) //True Console.WriteLine(o3==o4) //True Console.WriteLine(o3.Equals(o4)) //True Console.WriteLine(o5==o6) //False Console.WriteLine(o5.Equals(o6)) //True
如果不明白,建议详读【一、重载和重写的调用区别】
当然这里还涉及到另一个知识点:字符串常量与字符串变量的区别
字符串常量:形如我们通过string s1 = "bb" string s2 = "bb" 这时我们的s1和s2是指向同一个地址,这是因为它的初始值是一个常量,所以其地址分配在托管堆上的静态存储区,即所谓的常量池
字符串变量:形如我们通过String s1 = new String("aa") String s2 = new String("aa") ,这时我们的s1和s2指向的地址不一样,这是因为这是在托管堆上动态分配的地址
以上便是本人最近研究的关于c#的==和Equals的相关总结,如有不对之处,还望多多指正!