C#中关于字符串的一些特殊点
在C#中,字符串是一个使用比较多的类型,本文会讲到字符串(string)的一些特殊点。
定义
我们先来定义一个字符串类型变量。
string str = "这是一个字符串";
在上面的代码中,我们试用了 stirng
关键字作为类型来定义字符串变量,其实字符串真正的类型为 String
, string
是作为C#关键字的一个别名,在编码的时候,IDE会给提示,要求我们将 String
修改成 string
。
我们可以通过反编译来查看 String
的具体定义, String
其实是一个引用类型,派生于 Object
。但是我们在使用的过程中, String
又会表现出值类型的特性(多个变量即使指向了同一个值,在对其中一个变量修改时也不会污染到其他的变量)
public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable, IComparable<string>, IEnumerable<char>, IEquatable<string>
{
}
可以看到 String
类型的定义使用 sealed
关键字,表明 String
类型不可被当作基类继承。 这是因为CLR
对 String
类型做了一些特殊优化,必须防止自定义类型破坏了 CLR
的优化。
这里涉及到 String
类型的一个特性——不变性,即 String
对象一旦创建,就不能再更改.。不变性带来了另一个特性——一致性,即相同的字符串在 CLR
中在表现上同一个对象(其实不是)。这样在使用上, String
类型就和其他的值类型表现一致。不过由于这些特性的存在,所在如果对字符串进行多次修改操作会产生很多临时字符串对象,对性能有影响,所以在很多的库和最佳实践中,都会推荐使用 StringBuilder
来操作。
string str1 = "这是一个字符串";
string str2 = "这是一个字符串";
Console.WriteLine(object.Equals(str1,str2));
// True
Console.WriteLine(object.ReferenceEquals(str1,str2));
// False
可以看到在对象的层面上,两个字符串对象是一致的,这是因为 Equals
比较是靠 GethashCode
方法,而由于 CLR
的特殊优化, String
类型的 GetHashCode
方法会对同样的字符返回一样的哈希值。但是如果我们使用 ReferenceEquals
来比较两个对象的引用还是可以看到其实是两个对象,那么有没有办法做到字符串对象的复用呢?
答案是肯定的,我们只需要使用 Intern
方法即可。
在 CLR
初始化的时候,会构建一个内部表(暂存池)用来存放 String
对象的引用,而 Intern
方法就是用来访问内部表的,使用 Intern
方法的时候,会先检查内部表中有没有同样的字符串存在,如果有就会直接返回该字符串的引用。所以使用 Intern
方法获得的 String
对象在引用上也是相同的。
string str1 = "这是一个字符串";
string str2 = string.Intern("这是一个字符串");
Console.WriteLine(object.Equals(str1,str2));
// True
Console.WriteLine(object.ReferenceEquals(str1,str2));
// True
当然上面的测试都是在 .net
6的环境中进行的,如果 CLR
的实现不同,对于字符串的优化也有可能不同,甚至在 CLR
中默认开启字符串池,直接把相同值得字符串对象彻底优化成一个引用也是可能的。