到底什么是字符串的不变性?
string 类型是c#中使用最频繁的类型,因此CLR用专门的方法来处理、优化string,使得string虽然你是引用类型,但在表现上被.NET优化为值类型。
先看string的定义是:
public sealed class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string>
从这里我们可以得知:
string的本质是字符集合,因此,linq to object 的操作能作用在string上。
string是sealed,该特性是为字符串不变性(恒等性,immutability)和字符串驻留机制提供有效保证。(子类无法继承,因此无法破坏CLR对string的特殊处理机制)
这个特性的表现是,当对实例使用:
Insert(),PadLeft(),Remove(),Replace(),SubString(),ToUpper(),ToLower(),Trim()
等方法时,原有的字符串仍然在内存中,不被改变,对实例操作的结果需要在内存中创建新的字符串对象。
这样做的好处:
1)保证原string对象的稳定性。
2)string不会出现线程同步问题。
这样做的坏处:
性能和内存的双重。
为了应对这个缺点,CLR使用了哈希表类型的暂存池。
哈希表的key是string,value则是存储托管堆中的地址。当JIT编译方法时,会首先在哈希表中查找每一个字符串常量,如果找不到,则在表中创建一个键值对;如果找到,则将找到的键值对的value值赋给这个对象。
例子:
string strA = "abc"; string strB = "abc"; bool b=ReferenceEquals(strA, strB);//True
这边名strA和strB指向的是同一个地址。
两个方法
IsInterned和Intern
根据MSDN对IsInterned的描述:
此方法在暂存池中查找 str。 如果已经将 str 放入暂存池中,则返回对此实例的引用;否则返回 null。
例子:
string s1="abc"; string s2=string.IsInterned(s1); Console.WriteLine(s2);//"abc"
很奇怪的一点是:
string s1=string.IsInterned("def"); Console.WriteLine(s1); //"def"
预料中结果是null,因为"def"此时并不在暂存表中。结果打印出了"def"。猜测可能是当写"def"时,暂存池中已经将其加入了。
string s1 = "abc"; string s2 = s1+ "def"; string s3 = string.IsInterned(s2); Console.WriteLine(s3); //null
string s4 = "abcdef"; string s1 = "abc"; string s2 = s1+ "def"; string s3 = string.IsInterned(s2); Console.WriteLine(s3); //"abcdef"
而Intern的意思相似:
如果暂存了 str,则返回系统对其的引用;否则返回对值为 str 的字符串的新引用。
区别是:Intern如果在暂存池中查找不到该str时,则将该str添加到暂存池中,而IsInterned则不添加。
例子:
string strA = "abcdef"; string strB = "abc"; string strC = strB + "def"; var b1 = ReferenceEquals(strA, strC); //False,因为strC是动态构造的,因此这样的字符串不会被添加到暂存池中维护 strC = string.Intern(strC); //由于strC不在暂存池中,则将其添加进去。 var b2 = ReferenceEquals(strA, strC); //True
*本文依据《你必须知道的.NET》一书总结