比较值类型的相等性
代表:int
- ReferenceEquals():比较两个对象的引用,即地址。
- Equals():比较两个对象的内容。
- 比较运算符(==):比较两个对象的内容。
特例:struct无比较运算符(==)。
Code:
static void Main(string[] args)
{
int int_a = 3;
int int_b = 3;
Console.WriteLine("值类型测试");
Console.WriteLine("int_a.Equals(int_b):" + int_a.Equals(int_b));
Console.WriteLine("int_a==int_b:" + (int_a == int_b));
Console.WriteLine("object.ReferenceEquals(int_a, int_b):" + object.ReferenceEquals(int_a, int_b));
Console.WriteLine("");
TestStruct stru1 = new TestStruct();
TestStruct stru2 = new TestStruct();
Console.WriteLine("Struct 测试");
Console.WriteLine("stru1.Equals(stru2):" + stru1.Equals(stru2));
Console.WriteLine("stru1==stru2:compile error!");
Console.WriteLine("object.ReferenceEquals(stru1, stru2):" + object.ReferenceEquals(stru1, stru2));
Console.WriteLine("");
}
struct TestStruct
{
int one;
int two;
public TestStruct(int one, int two)
{
this.one = one;
this.two = two;
}
}
运行结果:
比较引用类型的相等性
代表:Class
- ReferenceEquals():静态方法,比较两个引用是为内存中的相同地址,然作为静态方法,它不能被重写。
public static bool ReferenceEquals(object objA, object objB);
-
虚拟的Equals():比较两个引用的地址,但可被重写为按照值来比较两个对象,重写的代码不会抛异常。重写Equals方法时需要重写hashcode,否则hashcode可能无法保持唯一性。
public virtual bool Equals(object obj);
- 静态的Equals():比较两个引用的地址,其区别为静态的带两个参数,并对它们进行相等性比较。
public static bool Equals(object objA, object objB);
- 比较运算符(==):比较两个对象的引用,会根据需要自动转换类型,也可被重载,但必须成对重载,即重载了“==”,就必须重载“!=”。
Code:
public class TestClass
{
int one = 1;
int two = 2;
}
static void Main(string[] args)
{
TestClass C1 = new TestClass();
TestClass C2 = new TestClass();
Console.WriteLine("Normal Class 测试");
Console.WriteLine("C1.Equals(C2):" + C1.Equals(C2));
Console.WriteLine("C1==C2:" + (C1 == C2));
Console.WriteLine("object.ReferenceEquals(C1, C2):" + object.ReferenceEquals(C1, C2));
Console.WriteLine("");
String str_a = "abc";
String str_b = "abc";
Console.WriteLine("Nomral string 测试");
Console.WriteLine("str_a.Equals(str_b):" + str_a.Equals(str_b));
Console.WriteLine("str_a==str_b:" + (str_a == str_b));
Console.WriteLine("object.ReferenceEquals(str_a, str_b):" + object.ReferenceEquals(str_a, str_b));
Console.WriteLine("");
String str1 = new string(new char[] { 'a', 'b', 'c' });
String str2 = new string(new char[] { 'a', 'b', 'c' });
Console.WriteLine("Special string 测试");
Console.WriteLine("str1.Equals(str2):" + str1.Equals(str2));
Console.WriteLine("str1==str2:" + (str1 == str2));
Console.WriteLine("object.ReferenceEquals(str1, str2):" + object.ReferenceEquals(str1, str2));
Console.WriteLine("");
}
运行结果:
重写Equals,==:
主函数代码如上,类代码如下:
public class TestClass
{
int one = 1;
int two = 2;
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
TestClass C1 = obj as TestClass;
//if (C1 == null)
//{
// return false;
//}
return (one == C1.one) && (two == C1.two);
}
public static bool operator ==(TestClass C1, TestClass C2)
{
if (C1.one == C2.one && C1.two == C2.two)
return true;
else
return false;
}
public static bool operator !=(TestClass C1, TestClass C2)
{
return !(C1 == C2);
}
public override int GetHashCode()
{
return base.GetHashCode() + 10;
}
}
运行结果:
以上代码有风险,因为重载了“==”,所以在执行以下图片代码会出错。注意重载有风险,所以在重载时需要谨慎。
//if (C1 == null)
//{
// return false;
//}
特例:两种类型的String
对应string类型,由以上代码及运行结果可知,直接给string赋值,若两个string的内容相同,则指向同一内存地址,而其他的引用类型(包括char数组赋值的string类型)却不是,原因在于直接赋值的string类型经过了享元模式的处理,即当多个string对象包含相同内容,则内存只创建一个string对象来对应不同的对象引用。
这也于string类型变量的内存开辟有关,因为其一旦被创造就不可改变(包括长度和其中的任何字符都不可改动),所以可以对其进行享元模式处理,而不必担心会出其他的问题。
拓展:
如下图代码,str_a看起来像是在之前开辟内存的对象上多加了一些内容,而实际是,它将之前的内容复制一份,又重新创建了一个新的同名的对象,并开辟了一块新的内存来存储这个新str_a的内容(之前同名变量的内容和新的内容“abc”),而之前的str_a对象内存将会被GC回收。
str_a += "abc";
StringBuilder是对于字符串和字符执行动态操作的类,在StringBuilder内部有一个足够长的字符数组用于存放字符串对象,当字符串长度没有超过字符数组的长度时,所有的操作均是针对于同一个字符数组,当长度超过时,才会创建一个更长的数组,把原先的数据复制到新数组中。这也是在对字符串进行频繁的修改时,相对于string对象系统开销会小很多的原因。