这些.Net的细节(面试秘笈),你都知道了吗?
Equals()和==之区别
首先,理解一下值类型和引用类型的区别:值类型存储在内存的栈中,引用类型变量的地址存储在栈中,而本身的内容存储在堆中(字符串是一种特殊的引用类型)。我们现在来理解Equals和==的区别就容易多了。Equals比较的是两个变量是否为同一个对象的引用,即堆中的内容是否相同。==比较的是两个变量的值是否相同,对引用类型来说则是两个变量的存储地址是否一致,即栈中的内容是否相同。(举个形象的例子:Equals比较的是文件夹中的内容是否一致,==比较的是文件夹的路径是否一致)
string s1 = "Miracle";
string s2 = "Miracle";
Console.WriteLine(s1==s2);//true
Console.WriteLine(s1.Equals(s2));//true
object o1 = s1;
object o2 = s2;
Console.WriteLine(o1 == o2);//true
Console.WriteLine(o1.Equals(o2));//true
string s3 = new string(new char[] { 'M', 'i', 'r', 'a', 'c', 'l', 'e' });
string s4 = new string(new char[] { 'M', 'i', 'r', 'a', 'c', 'l', 'e' });
Console.WriteLine(s3 == s4);//true
Console.WriteLine(s3.Equals(s4));//true
object o3 = s3;
object o4 = s4;
Console.WriteLine(o3 == o4);//false
Console.WriteLine(o3.Equals(o4));//true
Person p1 = new Person("Miracle");
Person p2 = new Person("Miracle");
Console.WriteLine(p1 == p2);//false
Console.WriteLine(p1.Equals(p2));//false
Person p3 = new Person("Miracle");
Person p4 = p3;
Console.WriteLine(p3 == p4);//true
Console.WriteLine(p3.Equals(p4));//true
可能大家对上述运行结果很迷惑。我在解释原因之前总结了几点值得大家注意的知识点,可以从字符串的内存分配开始:
1. 字符串虽然是一种引用类型(特殊),特殊在哪里呢?当存在多个包含同样值的字符串(如s1,s2)时,CLR编译器优化(可能)不会重复的分配内存而是指向同一个字符串内存实例。另外,字符串本身也重载了==,!=等运算符,使得适用于引用类型的==被重定义为比较字符串的内容是否一致而非比较是否为同一个引用地址。
2. 字符串一旦被分配内存,则对它本身的任何操作(如Trim,Replace等)均不会对本身产生任何影响(只要不更改后重新对本身赋值),反而会返回一个新的字符串实例来存放操作后的字符串。俗话说:字符串实例一旦生成,则到死都不会改变。
3. 基于防止字符串冗余分配内存的机制,CLR维护着一个叫驻留池(Intern Pool)的表。当声明一个字符串时,首先会到驻留池中检查是否已存在相同值时字符串,如果已存在则返回驻留池中的字符串,否则向驻留池中添加一个新的字符串。但值得注意的是,以其他方式生成相同值的字符串(如new,StringBuilder等)则不会进入驻留池,因而两者为不同的字符串实例。这也就是说在内存中可能存在多个副本的字符串的原因所在。
4. 为了能够检查字符串是否在驻留池中是否存在,提供了String.Intern()方法,原理是:将传入的字符串(可能是其他方式生成)实例在驻留池中查找是否有同值的字符串,如果存在则返回驻留池中的字符串;否则向驻留池中加入一个新的字符串。
5. 对于object类型,系统提供了Equals和ReferenceEquals方法分别检查内容一致和引用一致。其中ReferenceEquals类似于==的用法。
明白了以上的原理,下面我来一一解释一下:
对于s1,s2: 两者为同一个字符串实例(因为值相同),因此结果均为true;
对于o1,o2: 虽然是object,但是由于是s1,s2是同一个实例,因而o1,o2也回指向同一个字符串实例。
对于s3,s4: 由于采用new(其他方式)来为字符串分配内存,因而CLR会为s3,s4分别分配一块内存(尽管值相同,但不会进入驻留池)。由于字符串对==进行了重定义,因而s3==s4也是必然相等的。
对于o3,o4:由于存放的是来自不同内存块的字符串,因而两者不是同一个实例,故o3!=o4(我们可以通过object.ReferenceEquals(o3,o4)来判断)。
对于p1,p2: 两者完全是不同的对象,对于引用类型来说,p1!=p2那是绝对的,同时p1.Equals(p2)也是不相同的;对于p3,p4: 由于赋值使得成为同一个对象,后续不用多言。
const和readonly之区别
都用来表示不变的常量,那两者的区别是什么呢?
1. 初始化赋值不同。const必须在声明的同时赋值,readonly可在声明或构造函数中赋值。
2. const是在编译时就确定值,readonly是在运行时确定值。
3. const默认就是static的,readonly必须显式声明为static。
4. const只能为整形、浮点型、枚举型或引用类型(但只能为string或null),readonly可为任意类型。
5. object、array、struct不能声明为const。