原文地址:Jon Skeet:Strings in C# and .NET
System.String 类型(在C#语言中对应的别名是string)是.NET最重要的类型之一,不幸的是在它身上存在了太多的误解。这篇文章将试图去解决关于该类型的部分基础错误认知。
字符串是什么?
一个字符串实际上是一个字符序列。每一个字符都是范围介于U+0000至U+FFFF的Unicode字符(稍后给出更详细的说明)。string类型(后文中我将使用C#中的string别名统一来指代System.String类型)拥有以下特征:
-
它是一个引用类型
开发者中存在一个普遍的误解就是string类型是值类型。这常常是因为string的不变性使得其行为类似于值类型(见下一点)。实际上,它更多地表现为一个普通的引用类型。请查看我的参数传递和内存二文,以参阅关于值类型和引用类型之间差异的更多细节。 -
它是不可变的
你永远不可能改变字符串的内容,如果你使用不借助反射机制的安全代码的话。也正是因此,最终您通常只会更改字符串变量的值。例如,代码s = s.Replace(“foo”,“bar”);
不会更改s原来引用的字符串的内容——它只是将s的值设置到一个新字符串中,这个新字符串是旧字符串的副本,在这个新字符串中,“foo”将被替换为“bar”。 -
它可以包含空字符
C语言程序员习惯于使用' ',nul或者null字符来作为字符串字符序列的结尾。(我将使用“null”,因为它是Unicode代码图表中的详细信息;不要将它与C#中的null关键字混为一谈——char是值类型,所以它不能是一个空引用)在.NET中,字符串中可以包含空字符,就字符串本身具有的方法而言,这没有任何问题。然而,其他的类型(比如说许多Windows窗体)可能会认为字符串以第一个null字符作为结束标志——如果你的字符串表现为似乎会被奇数截断,可能就是出现了这种情况。 -
它重载了“”操作符
当操作符用于比较两个字符串时,Equals方法将被调用,该方法检查两个字符串内容的相等性,而不是引用本身。例如,即使操作符的两侧引用不同(指的是两个不同的字符串对象,它们都包含相同的字符序列),"hello".Substring(0,4)=="hell"
也将返回true。需要注意的是,如果操作符的两侧在编译时都是字符串表达式——操作符重载将仅在此处运行而不会以多态运行。如果操作的任意一边是object类型,则将应用正常的==操作符,并且简单的引用相等性将被测试。
字符串常量池(字符串驻留)
.NET有一个“字符串常量池”的概念。该常量池基本表现为一个字符串集合,但它确保每次引用具有相同值的字符串时,都会引用相同的字符串。这可能是在语言层面提供的,在C#和VB.NET中确实都是如此。所以如果看到有一种语言并不适用此规则(译者注:在.NET平台上),我将会非常惊讶,因为IL使其变得非常容易(实现此规则比不实现此规则更容易)。除了自动驻留的规则外,您还可以使用对应的Intern方法手动实现字符串驻留的功能,也可以使用IsInterned方法检查池中是否已经存在具有相同字符序列的内部字符串。这个方法返回一个字符串引用而不是一个布尔值,这稍微有些不直观——如果池中有相等的字符串,则返回对该字符串的引用,否则返回null。类似像Intern方法也会返回一个对驻留字符串的引用——例如暂存了“str”,则返回系统对其的引用;否则返回对值为“str”的字符串的新引用。
译者注,System.String的Intern和IsInterned方法将会在 .NET Core 2.0 版本释出。
字面值(Literals)
译者注:找不到合适的词语来解释Literals,所以取其英语翻译本意。
Literals就是你如何将字符串硬编码到C#程序中的方式。C#中有两种类型的字符串字面值方式——常规字符串字面值和逐字字符串字面值。常规字符串字面值与许多其他语言(例如Java和C)类似,它们以"
作为开始和结尾,并且各种字符(特别是"
本身,,以及回车(CR)和换行符(LF))需要转义成为在字符串中的表示。逐字字符串字面值允许字符串内部的几乎任何字符,并且在第一个字符
"
处不会结束(如果不成对实现)。即使回车和换行符也可以出现在字符串中!如果要获得一个"
字符,你需要写""
。逐字字符串字面值方式通过在字符串开头之前引用@
与常规字符串字面值方式进行区分。
译者注:这一段相对绕口,简而言之,Literals就是C#表示字符串的两种方式,以下给出示例解读。
/*
常规字符串字面值
*/
Console.WriteLine("This string contains a newline
and a tab and an escaped backslash\");
/*
逐字字符串字面值
*/
Console.WriteLine(@"This string displays as is. No newlines
, tabs or backslash-escapes\.");
/*
逐字字符串字面值,本句将打印 " 字符
*/
Console.WriteLine(@"""");
常规 | 逐字 | 结果 |
---|---|---|
"Hello" | @"Hello" | Hello |
"Backslash: " | @"Backslash: " | Backslash: |
"Quote: "" | @"Quote: """ | Quote: " |
"CRLF: Post CRLF" | @"CRLF:(换行)Post CRLF" | CRLF: (换行)Post CRLF |
请注意两种方式的区别仅在于编译器的行为。而一旦字符串已经处于编译代码中,字符串就不会再采用上述两种方式进行处理了。
完整的转义序列如下:
- ' - 单引号,字符需要
- " - 双引号,字符串需要
- - 反斜杠