首先得出一个结论:==是比较变量内存的数据,Equals是值比较。但是他们都能被重写,所以object又增加了一个RefrenceEquals不可被重写,只比较数据:
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), NonVersionable, __DynamicallyInvokable]
public static bool ReferenceEquals(object objA, object objB) =>
(objA == objB);//判断地址,由于是object,就算是值类型,也会装箱操作,所以变量存的就会是引用地址。
然后分类型区别对待:
引用类型:
作为引用类型的基类,在Object中的虚方法Equals,如下:[__DynamicallyInvokable]
public virtual bool Equals(object obj) =>
RuntimeHelpers.Equals(this, obj);//这个方法是引入了外部的函数,具体实现看不到,暂且认定为值相等。比如object a=1;objcet b=1,a.Equals(b)是true就是值相等,
但是==比较的是变量 a,b存的数据,也就是被装箱后的1,2的地址。所以a==b是false。
[__DynamicallyInvokable]
public static bool Equals(object objA, object objB) =>
((objA == objB) || (((objA != null) && (objB != null)) && objA.Equals(objB)));//不可重写的,除了判断==,还判断了Equals,但是Equals被重写后,就以重写的为主了。
值类型:
作为值类型的基类,ValueType,也对Equals做了重写。
[SecuritySafeCritical, __DynamicallyInvokable]
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
RuntimeType type = (RuntimeType) base.GetType();
RuntimeType type2 = (RuntimeType) obj.GetType();
if (type2 != type)//比较类型,不然可能值相等,类型不等,比如 int 与 long
{
return false;
}
object a = this;
if (CanCompareBits(this))
{
return FastEqualsCheck(a, obj);
}
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < fields.Length; i++)
{
object obj3 = ((RtFieldInfo) fields[i]).UnsafeGetValue(a);
object obj4 = ((RtFieldInfo) fields[i]).UnsafeGetValue(obj);
if (obj3 == null)
{
if (obj4 != null)
{
return false;
}
}
else if (!obj3.Equals(obj4))
{
return false;
}
}
return true;
}
//也是引入了一些外部函数,主要作用就是在值相等的基础上,增加了类型比较。至于==,由于变量内存的是数值本身,所以值类型时,==只比较变量的数值。
当然,尽管在valuetype中重写了Equals,在Int中还是重写了一次,效果也是一样,判断类型,以及类型转换后==比较值
Int32:
[__DynamicallyInvokable]
public override bool Equals(object obj) =>
((obj is uint) && (this == ((uint) obj)));
string类型:
//重写了==
[__DynamicallyInvokable]
public static bool operator ==(string a, string b) =>
Equals(a, b); //从地址对比,改成了调用自己的Equals
//重写的Equals
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), __DynamicallyInvokable]
public override bool Equals(object obj)//重写
{
if (this == null)
{
throw new NullReferenceException();
}
string strB = obj as string;
if (strB == null)
{
return false;
}
if (this == obj)
{
return true;
}
if (this.Length != strB.Length)
{
return false;
}
return EqualsHelper(this, strB);
}
//string类型
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), __DynamicallyInvokable]
public bool Equals(string value)
{
if (this == null)
{
throw new NullReferenceException();
}
if (value == null)
{
return false;
}
if (this == value)
{
return true;
}
if (this.Length != value.Length)
{
return false;
}
return EqualsHelper(this, value);
}
[__DynamicallyInvokable]
public static bool Equals(string a, string b)
{
if (a == b)
{
return true;
}
if ((a == null) || (b == null))
{
return false;
}
if (a.Length != b.Length)
{
return false;
}
return EqualsHelper(a, b);
}
//他们都调用了 EqualsHelper(a, b) 比较值
[SecuritySafeCritical, ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
private static unsafe bool EqualsHelper(string strA, string strB)
{
int length = strA.Length;
fixed (char* chRef = &strA.m_firstChar)
{
fixed (char* chRef2 = &strB.m_firstChar)
{
char* chPtr = chRef;
char* chPtr2 = chRef2;
while (length >= 10)
{
if (*(((int*) chPtr)) != *(((int*) chPtr2)))
{
return false;
}
if (*(((int*) (chPtr + 2))) != *(((int*) (chPtr2 + 2))))
{
return false;
}
if (*(((int*) (chPtr + 4))) != *(((int*) (chPtr2 + 4))))
{
return false;
}
if (*(((int*) (chPtr + 6))) != *(((int*) (chPtr2 + 6))))
{
return false;
}
if (*(((int*) (chPtr + 8))) != *(((int*) (chPtr2 + 8))))
{
return false;
}
chPtr += 10;
chPtr2 += 10;
length -= 10;
}
while (length > 0)
{
if (*(((int*) chPtr)) != *(((int*) chPtr2)))
{
break;
}
chPtr += 2;
chPtr2 += 2;
length -= 2;
}
return (length <= 0);
}
}
}//既然有字符串池,为什么还要重写==和Equals呢?
请看下段官网代码:
public class Example
{ public static void Main()
{ String s1 = "String1";
String s2 = "String1";
Console.WriteLine("s1 = s2: {0}",
Object.ReferenceEquals(s1, s2));
Console.WriteLine("{0} interned: {1}", s1, String.IsNullOrEmpty(String.IsInterned(s1)) ? "No" : "Yes");
String suffix = "A"; String s3 = "String" + suffix;
String s4 = "String" + suffix;
Console.WriteLine("s3 = s4: {0}", Object.ReferenceEquals(s3, s4));
Console.WriteLine("{0} interned: {1}", s3, String.IsNullOrEmpty(String.IsInterned(s3)) ? "No" : "Yes"); } }
// The example displays the following output: // s1 = s2: True // String1 interned: Yes // s3 = s4: False // StringA interned: No
当两个字符串变量都只想同一个静态的字符串时,refrenceEquals都是true,
当两个变量时运算的结果相等时,refrenceEquals是不相等的。
综上所述:
在值类型中 ==是值比较 Equals是值比较+类型比较
在引用类型中 ==是比较引用,equals是比较值(不过自定义类,都是不相等的,需要自己重写。object装箱操作后,== 是false,Equals是true)
在自定义类中,都可以对==和Equals重写,不过重写==必须重写!=, 否则报错,重写Equals建议重写gethascode,否则在用到dictionary和hashtable时会出bug,针对于他们都能重写,Object提供了referenceequsls(),作为地址比较。
结论:
==和Equals的区别只有在装箱操作下,才会出现 == 是false Equals是 true,自定义类需要重写,建议重写Equals,留==作为引用比较。
扩展:为什么重写Equals后,要重写GetHashCode()呢?
因为在Dictionary和HashTabble中都是键值对的存储,Key就是他们的HashCode,如果不重写就会导致Equals相等而HashCode不相等,你存入时简单,获取时就找不到了。