先给结论:
对于每种类型创建2个一样的数据,比较结果如下表所示:
数据类型 | == | Equals | ReferenceEquals |
---|---|---|---|
int(值类型) | √ | √ | × |
引用类型 | × | × | × |
引用类型(带override) | 以operator ==实现为准 | 以Equals覆写为准 | × |
struct | 必须实现==操作符 | √ | × |
struct(带override) | 以operator ==实现为准 | 以Equals覆写为准 | × |
string | √ | √ | × |
一.比较操作符==
对于==的相等性检测,思路如下:
- 如果是struct,且没实现==操作符,则编译报错:
- 如果实现了 == 操作符,则以 == 操作为准
- 如果是系统基础值类型,则可以直接判断值是否相等
- 如果是引用类型,则比较它们的引用是否相等,也就是栈上的保存的地址
public void Case1()
{
TestClass testClass1 = new TestClass();
TestClass testClass2 = new TestClass();
Console.WriteLine($"testClass2 == testClass1 :{testClass2 == testClass1}"); //输出: false
}
- 如果是string,因为string实现了==操作符,实际比较的是stirng内保存的内容是否相等
这里可以看到C#对于 == 与Equals的实现,其实比较的实际存储的数据
public static bool operator == (String a, String b) {
return String.Equals(a, b);
}
public static bool Equals(String a, String b) {
if ((Object)a==(Object)b) {
return true;
}
if ((Object)a==null || (Object)b==null) {
return false;
}
if (a.Length != b.Length)
return false;
return EqualsHelper(a, b);
}
private unsafe static bool EqualsHelper(String strA, String strB)
{
Contract.Requires(strA != null);
Contract.Requires(strB != null);
Contract.Requires(strA.Length == strB.Length);
int length = strA.Length;
fixed (char* ap = &strA.m_firstChar) fixed (char* bp = &strB.m_firstChar)
{
char* a = ap;
char* b = bp;
// unroll the loop
#if AMD64
// for AMD64 bit platform we unroll by 12 and
// check 3 qword at a time. This is less code
// than the 32 bit case and is shorter
// pathlength
while (length >= 12)
{
if (*(long*)a != *(long*)b) return false;
if (*(long*)(a+4) != *(long*)(b+4)) return false;
if (*(long*)(a+8) != *(long*)(b+8)) return false;
a += 12; b += 12; length -= 12;
}
#else
while (length >= 10)
{
if (*(int*)a != *(int*)b) return false;
if (*(int*)(a+2) != *(int*)(b+2)) return false;
if (*(int*)(a+4) != *(int*)(b+4)) return false;
if (*(int*)(a+6) != *(int*)(b+6)) return false;
if (*(int*)(a+8) != *(int*)(b+8)) return false;
a += 10; b += 10; length -= 10;
}
#endif
// This depends on the fact that the String objects are
// always zero terminated and that the terminating zero is not included
// in the length. For odd string sizes, the last compare will include
// the zero terminator.
while (length > 0)
{
if (*(int*)a != *(int*)b) break;
a += 2; b += 2; length -= 2;
}
return (length <= 0);
}
}
二. Equals方法
在C#中,Equals是一个虚拟方法,它用于比较对象的内容是否相等。Equals方法是从System.Object基类继承而来,因此所有的C#类型都继承了Equals方法,但是默认情况下,对于引用类型而言,Equals方法只比较它们的引用是否相等,而不比较对象的内容。
- 覆写了Equals方法:以方法实现为准
- 基础值类型,struct:默认比较栈上存储的值是否相等
public void Case2()
{
TestStruct testStruct1 = new TestStruct()
{
_int = 10,
_bool = false,
};
TestStruct testStruct2 = new TestStruct()
{
_int = 10,
_bool = false,
};
Console.WriteLine($"testStruct1.Equals(testStruct2) :{testStruct1.Equals(testStruct2)}"); //输出: true
}
- 引用类型:如果对象引用一致(栈内容),则相等
public void Case1()
{
TestClass testClass1 = new TestClass();
TestClass testClass2 = new TestClass();
Console.WriteLine($"testClass2.Equals(testClass1) :{testClass2.Equals(testClass1)}");
}
- string类型:跟==实现一样,都覆写了Equals方法,比较的是实际内容
三.ReferenceEquals方法
在C#中,ReferenceEquals是一个静态方法,用于确定两个引用变量是否引用同一个对象(即两个引用是否指向同一内存地址)。ReferenceEquals方法是在System.Object类中定义的
- 对于值类型,默认会将值类型先装箱,转换成Object类型,这相当于创建了2个新对象,肯定不相等
public void Case3()
{
int int1 = 10;
int int2 = 10;
Console.WriteLine($"ReferenceEquals(int1, int2) :{ReferenceEquals(int1, int2)}"); //输出: false
}
- 对于string与引用类型,则,直接比较的是引用(栈上保存)是否一致
public void Case5()
{
string str1 = "Hello";
string str2 = "Hello";
Console.WriteLine(ReferenceEquals(str1, str2)); // 输出: false
string str3 = str1;
Console.WriteLine(ReferenceEquals(str1, str3)); // 输出: true
}
使用方法
- 对于系统内置值类型:直接使用==即可,也可以使用Equals,但是不能使用ReferenceEquals(装箱GC,且不能判断相等)
- 对于结构体:建议自定义实现操作符==,且覆写Equals方法
- 对于引用类型:如果只需要判断引用相当,都可以使用,但是如果要判断数据内保存的内容相等,则需要自己实现操作符==,且覆写Equals方法
- 对于string:默认全部都是比较的内容相等性