- 本博客所总结书籍为《CLR via C#(第4版)》清华大学出版社,2021年11月第11次印刷(如果是旧版书籍或者pdf可能会出现书页对不上的情况)
- 你可以理解为本博客为该书的精简子集,给正在学习中的人提供一个“glance”,以及对于部分专业术语或知识点给出解释/博客链接。
- 【本博客有如下定义“Px x”,第一个代表书中的页数,第二个代表大致内容从本页第几段开始。(如果有last+x代表倒数第几段,last代表最后一段)】
- 电子书可以在博客首页的文档-资源归档中找到,或者点击:传送门自行查找。如有能力请支持正版。(很推荐放在竖屏上阅读本电子书,这多是一件美事)
- 欢迎加群学习交流:637959304 进群密码:(CSGO的拆包密码)
该部分可以留意语言差异的解决方案操作,书中有详细解释但比较零散。本文不作为重点进行整合介绍。
第十四章 字符、字符串和文本处理
字符
- 在.NET Framework 中,字符总是表示成16位.Unicode代码值,这简化了国际化应用程序的开发。每个字符都是System.Char结构(一个值类型)的实例。System.Char类型很简单,提供了两个公共只读常量字段:MinValue(定义成’\0′)和 MaxValue(定义成’\uffff)。(P279 2)
- 为了简化开发,Char类型还提供了几个静态方法,包括IsDigit, IsLetter,IsUpper,IsLower,IsPunctuation,IsLetterOrDigit,IsControl,IsNumber,IsSeparator ,IsSurrogate ,IsLowSurrogate , lsHighSurrogate和IsSymbol。(P279 last)
- 各种数值类型与char实例的相互转换:(P280 last)
1、转型(强制类型转换)
2、使用Convert类型
3、使用IConvertible接口(效率最差,因为需要装箱操作)
public static void Main()
{
char c;
int n;
c = (char)65;
n = (int)c;
c = unchecked((char)(65536+65))
c = Convert.Tochar(65);
n = Convert.Toint(c);
c = ((IConvertible)65).Tochar(null);
n = ((IConvertible)c).Toint(null);
}
System.String类型
- 一个String代表一个不可变(immutable)的顺序字符集。String直接派生自Object,所以是引用类型,存在于堆上,永远不回去线程栈。String类型还实现了几个接口(IComparable/IComparable,ICloneable,IConvertible,IEnumerablellEnumerable和 IEquatable)。(P282 last3)
- C#不允许用new构造string对象,而是直接赋值即可。
- string允许用‘+’来直接拼接字符串。对于字面值进行加法拼接操作时,C#能在编译时连接,而对于非字面值,连接在运行时进行,这样会在堆上创建多个字符串对象,因此不推荐使用。(P284 1)
- 逐字字符串‘@’
//‘\’在原本代表中是转义字符,所以两个\才能代表原本的路径符号\,使用@会告诉编译器这是逐字字符串不会对转义字符进行翻译
string file = "C:\\Windows\\System32";
string file = @"C:\Windows\System32";
- String对象最重要的一点就是不可变(immutable)。也就是说,字符串一经创建便不能更改,不能变长、变短或修改其中的任何字符。使字符串不可变有几方面的好处:1、它允许在一个字符串上执行各种操作,而不实际地更改字符串。2、操纵或访问字符串时不会发生线程同步问题,3、CLR可通过一个String对象共享多个完全一直的String内容,以此减少系统中的字符串数量从而节省内存,即“字符串留用”(string interning)。(P284 last)
- 比较字符串CompareTo,不同语言差异的字符串比较(P286 last)
- 字符串留用:如果应用程序经常对字符串进行区分大小写的序号比较,或者事先知道许多字符串对象都有相同的值,就可利用CLR的字符串留用(string interning)机制来显著提升性能。CLR初始化时会创建一个内部哈希表。在这个表中,键(key)是字符串,而值(value)是对托管堆中的String对象的引用。哈希表最开始是空的(理应如此)。String 类提供了两个方法,便于你访问这个内部哈希表:(P291 3)
//第一个方法 Intern获取一个String,获得它的哈希码,并在内部哈希表中检查是否有相匹配的。如果存在完
//全相同的字符串,就返回对现有String对象的引用。如果不存在完全相同的字符串,就创建字符串的副本,
//将副本添加到内部哈希表中,返回对该副本的引用。如果应用程序不再保持对原始String对象的引用,垃圾
//回收器就可释放那个字符串的内存。注意垃圾回收器不能释放内部哈希表引用的字符串,因为哈希表正在
//容纳对它们的引用。除非卸载AppDomain或进程终止,否则内部哈希表引用的String对象不能被释放。
public static String Intern (String str);
//和 Intern方法一样,IsInterned方法也获取一个String,并在内部哈希表中查找它。如果哈希表中有匹配的
//字符串,IsInterned 就返回对这个留用(intermed)字符串对象的引用。但如果没有,IsInterned会返回null,
//不会将字符串添加到哈希表中。
public static string IsInterned(String str) ;
string s1 ="Hello";
string s2 ="Hello";
Console.writeLine (object.ReferenceEquals(sl,s2)); //显示'False',因为引用不同
s1 = string.Intern (s1) ;
s2 =string .Intern (s2) ;
console.writeLine (object.ReferenceEquals(sl,s2));//显示'True'
- 编译源代码时,编译器必须处理每个字面值(literal)字符串,并在托管模块的元数据中嵌入。同一个字符串在源代码中多次出现,把它们都嵌入元数据会使生成的文件无谓地增大。
字符串池:为解决该问题,C#编译器只在模块的元数据中只将字面值字符串写入一次。引用该字符串的所有代码都被修改成引用元数据中的同一个字符串。编译器将单个字符串的多个实例合并成一个实例,能显著减少模块的大小。(P293 2) - 检查字符串中的字符和文本元素:String类型提供了几个属性和方法 :Length,Chars(一个C#索引器),GetEnumerator,ToCharArray,Contains,IndexOf,LastIndexOf,IndexOfAny 和LastIndexOfAny.(P293 3)
- 其他字符串操作:String还提供了多个用于处理字符串的静态方法和实例方法,比如Insert,Remove,PadLeft,Replace,Split,Join,ToLower,ToUpper,Trim,Concat,Format等。使用所有这些方法时都请牢记一点,它们返回的都是新的字符串对象。这是由于字符串是不可变的。一经创建,便不能修改(使用安全代码的话)。(P295 last)

高效构造字符串
- 由于String类型代表不可变字符串,所以FCL提供了System.Text.StringBuilder类型对字符串和字符进行高效动态处理,并返回处理好的String对象。从逻辑上说,StringBuilder对象包含一个字段,该字段引用了由 Char 结构构成的数组。可利用StringBuilder的各个成员来操纵该字符数组,高效率地缩短字符串或更改字符串中的字符。如果字符串变大,超过了事先分配的字符数组大小,StringBuilder 会自动分配一个新的、更大的数组,复制字符,并开始使用新数组。前一个数组被垃圾回收。(P296 1)
- C#不将StringBuilder视为基元类型,StringBuilder对象用new来创建。(P296 4)
StringBuilder类的关键概念:
1、最大容量:一个int值,指定了能放到字符串中的最大字符数。默认为0x7ffffff。
2、容量:一个int值,指定了由StringBuilder维护的字符数组的长度。默认为16。
3、字符数组:一个由char结构构成的数组,负责维护“字符串”的字符内容。 - StringBuilder大多数成员都能更改字符数组的内容,同时不会造成在托管堆上分配新对象。StringBuilder只有两种情况才会分配新对象:1、动态构造字符串,其长度超过了设置的容量 2、调用StringBuilder的ToString方法。(P297 2)
- StringBuilder成员


获取对象的字符串表示:ToString
- 可调用ToString 方法获取任何对象的字符串表示。System.Object定义了一个 public、virtual、无参的ToString 方法,所以在任何类型的实例上都能调用该方法。(P299 2)
- ToString重写及指定格式处理(P300 3)
- 将多个对象格式化成一个字符串:String 的静态Format方法获取一个格式字符串。在格式字符串中,大括号中的数字指定了可供替换的参数。本例的格式字符串告诉Format方法将{0}替换成格式字符串之后的第一个参数(日期/时间),将{1}替换成格式字符串之后的第二个参数(“Aidan”),将{2}替换成格式字符串之后的第三个参数(9)。
string s = string.Format ("on {0},{1) is i2} years old.",new DateTime (2012,4,22,14,355),"Aidan",9);
console.writeLine (s);
- 利用AppendFormat处理指定内容的处理方法(P304 last)
解析字符串来获取对象:Parse
- Parse的使用方法如下,int和datetime类型提供了多种重载方式以简化代码书写。(P307 2)
Int32 x = Int32.Parse (" 123",Numberstyles.None,null) ;
//允许跳过前导空白符
Int32 x = Int32.Parse (" 123",Numberstyles.AllowLeadingWhite,null) ;
//解析16进制
Int32 x = Int32.Parse ( "1A",NumberStyles.HexNumber,null);
编码:字符和字节的相互转换
- 要将字符串保存到文件中,或者通过网络传输。如果字符串中的大多数字符都是英语用户用的,那么保存或传输一系列16位值,效率就显得不那么理想,因为写入的半数字节都只由零构成。相反,更有效的做法是将16位值编码成压缩的字节数组,以后再将字节数组解码回16位值的数组。(P308 last3)
- FCL提供提供了一些类型来简化字符编码和解码,例如UTF-16和UTF-8:(还支持UTF-32,UTF-7,ascii等)(P309 2)
1、UTF-16将每个16位字符编码成2个字节,不对字符产生任何影响,也不会发生压缩。UTF-16也成为Unicode编码,有低位优先和高位优先的区别。
2、UTF-8将部分字符编码成1-4个字节。值在0x0080之下的字符压缩成1个字节,适合表示美国使用的字符。0x0080~0x07FF的字符转换成2个字节,适合欧洲和中东语言。0x0800以及之上的字符转换成3个字节,适合东亚语言。最后,代理项对(surrogate pair)表示成4个字节。UTF-8编码方案非常流行,但如果要编码的许多字符都具有0x0800或者之上的值,效率反而不如UTF-16。
string s = "asduas";
Encoding toUTF8 = Encoding.UTF8;
//将字符串编码成字节数组
Byte[ ] encodedBytes = encodingUTF8.GetBytes (s) ;
//将字节数组解码回字符串
string decodedstring = encodingUTF8.Getstring (encodedBytes) ;
- 除了UTF8静态属性,Encoding类还提供了以下静态属性: Unicode,BigEndianUnicode,UTF32,UTF7,ASCII和 Default。Default属性返回的对象使用用户当前的代码页来进行编码/解码。(P310 3)
- Encoding及字符转换相关方法的性能比较(P211 2)

- 假定现在要通过System.Net.Sockets.NetworkStream对象来读取一个UTF-16编码字符串。字节流通常以数据块(data chunk)的形式传输。换言之,可能是先从流中读取5个字节,再读取7个字节。UTF-16的每个字符都由2个字节构成。所以,调用Encoding 的GetString方法并传递第一个5字节数组,返回的字符串只包含2个字符。再次调用GetString 并传递接着的7个字节,将返回只包含3个字符的字符串。显然,所有code point都会存储错误的值!(P313 1)
字符字节流的编码和解码:使用Decoder派生类。它会尽可能多地解码字节数组。假如字节数组包含的字节不足以完成一个字符,剩余的字节会保存到 Decoder对象内部。下次调用其中一个方法时,Decoder对象会利用之前剩余的字节再加上传给它的新字节数组。这样一来,就可以确保对数据块进行正确解码。从流中读取字节时 Decoder对象的作用很大。(P314 2)
从Encoding派生的类型可用于无状态(中途不保持状态)编码和解码。而从Decoder派生的类型只能用于解码。(P314 3) - Base-64编码和解码(P314 last)
安全字符串
- String 对象可能包含敏感数据,比如用户密码或信用卡资料。遗憾的是,String 对象在内存中包含一个字符数组。如果允许执行不安全或者非托管的代码,这些代码就可以扫描进程的地址空间,找到包含敏感数据的字符串,并以非授权的方式加以利用。(P315 2)
- 更安全的字符串类:System.Security.SecureString。构造该对象时,会在内部分配一个非托管内存块,其中包含一个字符数组(经过加密),从而避开垃圾回收器。(P315 3)
在调用该类型系相关字符串操作AppendChar,InsertAt,RemoveAt和SetAt仍会有解密字符操作,所以应尽量减少这些操作。(P315 last2)
SecureString 类实现了IDisposable接口,允许以简单的方式确定性地摧毁字符串中的安全内容。应用程序不再需要敏感的字符串内容时,只需调用SecureString 的 Dispose方法。在内部,Dispose 会对内存缓冲区的内容进行清零,确保恶意代码无法获得敏感信息,然后释放缓冲区。