C# 中==运算符和equals函数的比较方式的区别
显然这是两个用的对数据经行比较的方法。
但两者是有区别的,熟悉C/C++的朋友们一定有对地址和值这两个概念经行比较深入的研究。但是C#为了安全起见,把地址(也就是指针)这个东西给取消了,取而代之的是对象的引用(其实这个也是在栈上的和地址所处的地方是一样的)。好了,现在我们来看==和equals的区别。
1.从最简单的值类型入手
1 int a = 1; 2 int b = 1; 3 Console.WriteLine(a.Equals(b)); 4 Console.WriteLine((a == b).ToString());
结果是:True True
这是很显然的,因为==在值类型中是有明确意义(关于这点是与引用对象有区别的)的,equals同样也是比较两者的值(equals的具体比较方法我将在后面说到)是否一样,这里没有涉及到地址的问题。
同理,其他值类型都是一样的(string 这个类有点例外,待会再最后我将详细说的)。
2.对于引用类型
1 class Person 2 { 3 public string Name 4 { 5 get; 6 set; 7 } 8 } 9 static void Main(string[] args) 10 { 11 Person per1=new Person(){Name="zhangsan"}; 12 Person per2=new Person(){Name="zhangsan"}; 13 Console.WriteLine((per1 == per2).ToString()); 14 Console.WriteLine(per1.Equals(per2)); 15 }
结果是:False False
这里结果都是False,在这它们的比较的机制也差不多的 == 是比较的引用的地址是否相等,equals是看两个引用变量是否引用同一个对象。说白了,就是看他们是不是一个“人”;
3.对于“特殊”类型
1 static void Main(string[] args) 2 { 3 string str1 = "zhangsan"; 4 string str2 = "zhangsan"; 5 Console.WriteLine(str1 == str2); 6 Console.WriteLine(str1.Equals(str2)); 7 Console.WriteLine((object)str1==(object)str2); //这里是比较它们的地址值 8 }
结果是:True True True
到这里读者可能就有疑问了,为什么str1和str2的地址也是一样的呢。
好,下面我就帮大家系统的分析下上面所有代码产生的结果的原因了。
1.对于equals
查看MSDN中equals方法中有这么一句话:
Equals 的默认实现支持引用相等性(对于引用类型)和按位相等性(对于值类型)。引用相等性是指进行比较的多个对象引用所引用的是同一个对象。按位相等性是指进行比较的多个对象具有相同的二进制表示形式。
这句话说明了什么?不就是说明了它的比较机制吗。对于值类型的它是按位进行比较的,对于引用类型的它是看他们是否指向同一个对象。
关于equals的比较方法也就明了了。
2.对于==
在C#中,很多引用类型都是重载了==运算符的,对于引用对象,对象是在堆上建立的,而引用变量的值(也就是堆上对象的地址)是在栈上的。
而==就是比较栈上的内容是否相同的。
3.对于“特殊类型”
这里也就是比较特殊的地方 而且是单对string类来说的。
下面我加一段代码:
1 string str1 = "zhangsan"; 2 string str2 = "zhangsan"; 3 string str3 = new string(new char[] { 'z', 'h', 'a', 'n', 'g' }); 4 string str4 = new string(new char[] { 'z', 'h', 'a', 'n', 'g' }); 5 Console.WriteLine("str1 == str2 " + (str1 == str2).ToString()); 6 Console.WriteLine("str1 Equals str2 " + str1.Equals(str2)); 7 Console.WriteLine("(object)str1 == (object)str2 " + ((object)str1 == (object)str2).ToString()); 8 Console.WriteLine("str3 == str4 " + (str3 == str4).ToString()); 9 Console.WriteLine("str3 Equals str4 " + str3.Equals(str4)); 10 Console.WriteLine("(object)str3 == (object)str4 " + ((object)str3 == (object)str4).ToString());
运行结果是这样的:
前5个全是True 只有最后一个是False
原因是这样的:
String类有很多特别的地方,其中之一就是它是“不会改变的”(immutable)。这说明在我们每次对一个String对象进行操作时(比如说使用Trim,Replace等方法),并不是真的对这个String对象的实例进行修改,而是返回一个新的String对象实例作为操作执行的结果。String对象的实例一经生成,到死都不会被改变了!
基于String类这样的特性,CLR让表示相同的字符串实际值的变量指向同一个String事例,就是完全合理的了。因为利用任何一个对String实例的引用所进行的修改操作都不会切实地影响到该实例的状态,也就不会影响到其他所有指向该实例的引用所表示的字符串实际值。CLR如此管理String类的内存分配,可以优化内存的使用情况,避免内存中包含冗余的数据。
(这段话摘自http://www.cnblogs.com/instance/archive/2011/05/24/2056091.html)
简单点说就是,既然string 着各类这个类不可变,把我们就让同样的字符串,都指向同一个堆上的资源,这样不就节省的内存资源吗?何乐而不为。
所以CLR就维护了一个暂存池(intern pool)在里面就有所有的已使用的字符串资源。
但是并不是所以相同的字符串都是指向同一资源的,只有那些显式地使用字面量来赋值的时候它才会自动去检查有没有同样字符串的资源,就像前面的“zhangsan"一样。而使用
string str4 = new string(new char[] { 'z', 'h', 'a', 'n', 'g' }); 就不会检查了。
如果你任然想让它去检查的话,可以使用Intern类的方法。
比如这里改成
string str4 = string.Intern(str3);
这样他两就指向相同的资源了。