我们提供安全,免费的手游软件下载!

安卓手机游戏下载_安卓手机软件下载_安卓手机应用免费下载-先锋下载

当前位置: 主页 > 软件教程 > 软件教程

C#.Net筑基-String字符串超全总结 [深度好文]

来源:网络 更新时间:2024-06-06 09:32:34

字符串是日常编码中最常用的引用类型了,可能没有之一,加上字符串的不可变性、驻留性,很容易产生性能问题,因此必须全面了解一下。


01、字符与字符编码

1.1、字符Char

字符 char 表示为 Unicode字符,在C#中用 UTF-16 编码表示,占用2个字节(16位)大小,字面量用单引号 '' 包裹。

char c = 'A';
Console.WriteLine(char.IsDigit('3'));
Console.WriteLine(char.IsNumber('1'));
Console.WriteLine(char.IsLetter('A'));
Console.WriteLine(char.IsLower('a'));
Console.WriteLine(char.IsUpper('A'));
Console.WriteLine(char.GetUnicodeCategory('A')); //获取字符分类
  • char 是值类型(结构体),以16位整数形式存储, char 可隐式转换为 int
  • 字符串可以看做是 char 序列(数组),字符串是引用类型。

string str = "Hello World";
Console.WriteLine(str[0]);  //H
Console.WriteLine(str[10]); //d
Console.WriteLine(str[0].GetType().Name); //Char

1.2、字符集Unicode与字符编码

一般情况下字符串长度 string.Length 就是可见的文本字符数量,但这并不绝对相等。大多数字符都是一个char组成,然而有些字符无法用一个char表示,如表情、不常用字符等,他们会用两个char(4个字节)来表示。

"a".Length.Dump();    //1
"?".Length.Dump();  //2
"?".Length.Dump();  //2
"⏰".Length.Dump();  //1
"你好".Length.Dump(); //2
"臢".Length.Dump();   //1
$"{(int)'A':X4}".Dump(); //0041
//上面的dump() 是一个扩展方法,作用同Console.WritLine()

Unicode 是国际标准、通用字符集,涵盖了世界上几乎所有的文字、符号,可以满足跨平台、跨语言的文本信息编码。Unicode 有100W+个字符地址空间,地址范围是 0x0000 - 0x10FFFF,每个字符都有自己的编码,目前已分配了大约10W+个。通常使用“U+”后跟一个十六进制数来表示,例如字母 A 的Unicode码点是 U+0041

Unicode 字符集中包含多个分类(平面):其中最常用的就是基本平面,大部分常用字符都在这里面。

  • ? 基本多文种平面(BMP,Basic Multilingual Plane) :Unicode 的BMP区域几乎包含了所有常用的字符,如几十种主流语言,及30000+的汉字,BMP区域的字符都只需要1个 char (2个字节)表示。
  • ? 辅助平面(SMP) :包含其他不常使用的字符,如一些历史文字、音乐符号、数学符号和表情符号等。该区域大多用两个 char (4个字节)表示一个符号。

Unicode 是一种字符集,而实际在计算机上存储时需要用一个确定的编码方案,常见的就是UTF-8、UTF-16、UTF32。

  • UTF-16 :2个字节表示BMP中的字符,其他字符会需要4个字节,C#、Java语言内部就是使用的UTF-16来表示的字符串。
  • UTF-8 :变长编码,使用1到4个字节来表示一个Unicode字符,在互联网使用广泛。特别是存储 ASCII 为主的内容时,变长编码可以显著节约存储空间。

?ASCII 字符集只包含 128个 基础字符,涵盖键盘上的字母、数字、常用符号。Unicode 是包含 ASCII字符集的,最前面128 个字符就是。在UTF-8编码中 ASCII字符只需要1个字节。


02、String基础

字符串 string 是一个不可变(不可修改)的字符序列(数组),为引用类型,字面量用双引号 "" 包裹。

string s1 = "sam";
string s2 = new string('1',5);//11111
Console.WriteLine(s2[0]); //像数组一样操作字符串中的字符
string s3 = "";
string s4 = string.Empty; //效果同上
//相等比较
object s1= "Hello".Substring(0,2);
object s2 = "Hello".Substring(0,2);	
(s1==s2).Dump();        //False
(s1.Equals(s2)).Dump(); //True
  • 字符串是引用类型,因此可以用 null 表示,不过一般空字符建议用 string.Empty (或 "" )表示。
  • 字符串可以当做 字符数组一样操作,只是不能修改。
  • 字符串的相等为 值比较 ,只要字符序列相同即可。例外情况请是如果用 object == 比较,只会比较引用地址。

? 字符串在存储、转换为字节码时需指定编码,一般默认为 UTF-8,这是广泛使用的编码类型,更节省空间。

2.1、字符串常用API

属性 特点/说明
Length 字符串中字符数量
索引器[int index] 索引器,用索引获取字符,不可修改
?方法 特点/说明
StartsWith 、 EndsWith(String) 判断开头、结尾是否匹配, "Hello".StartsWith("He")
Equals(String) 比较字符串是否相同
IndexOf() 查找指定字符(串)的索引位置,从后往前查找 LastIndexOf
Insert(Int32, String) 指定位置插入字符串,‼️返回新字符串!
PadLeft(Int32) 指定字符宽度(数量)对齐,左侧填充,‼️返回新字符串!右侧填充 PadRight(Int32)
Remove(Int32, Int32) 删除指定位置、长度的字符,‼️返回新字符串!
Replace (String, String) 替换指定内容的字符(串),‼️返回新字符串!
Substring (Int32, Int32) 截取指定位置、长度的字符串,‼️返回新字符串!
ToLower() 、 ToUpper() 返回小写、大写形式的字符串,‼️返回新字符串!
Trim () 裁剪掉前后空格,‼️返回新字符串!有多个配套方法 TrimEnd 、 TrimStart
Split(char) 按分隔符分割字符串为多个子串,比较常用,不过性能不好,建议用Span代替。
?静态方法 特点/说明
Empty 获取一个空字符串(同 ""
Compare(String, String) 比较两个字符串,有很多重载,返回一个整数,0表示相同。
Concat (params string?[]) 连接多个字符串,返回一个新的字符串,有很多重载,是比较基础的字符串连接函数。
Equals(str, StringComparison) 比较字符串是否相同,可指定比较规则 StringComparison
Format (String, Object[]) 字符串格式化,远古时期常用的字符串格式化方式,现在多实用$插值
string Intern(String) 获取“内部”字符串,先检查 字符串池 中是否存在,有则返回其引用,没有则添加并返回
string? IsInterned(String) 判断是否在 字符串池 中,存在则返回其引用,没有则返回 null
IsNullOrEmpty (String) 判断指定的字符串是否 null 、空字符 "" / String.Empty ,返回bool
IsNullOrWhiteSpace (String) 判断指定的字符串是否 null 、空字符 "" / String.Empty 、空格字符,返回bool
Join (Char, String[]) 用分隔符连接一个数组为一个字符串

2.2、字符串的不变性、驻留性

字符串是一种有一点点特别的引用类型,因为其不变性,所以在参数传递时有点像值类型。

  • ?不变性: 字符串一经创建,值不可变。对字符串的各种修改操作都会创建新的字符串对象,这一点要非常重视,应尽量避免,较少不必要的内存开销。
  • ?驻留性 :运行时将字符串值存储在“驻留池(字符串池)”中,相同值的字符串都复用同一地址。

不变性、驻留性 是 .Net 对string 的性能优化,提升字符串的处理性能。如下示例中,s1、s2字符串是同一个引用。

string s1 = "hello";
string s2 = "hello";
Console.WriteLine(s1 == s2);                      //True
Console.WriteLine(s1.Equals(s2));                 //True
Console.WriteLine(Object.ReferenceEquals(s1,s2)); //True

当然不是所有字符串都会驻留,那样驻留池不就撑爆了吗!一般只有两种情况下字符串会被驻留:

  • 字面量的字符串,这在编译阶段就能确定的“字符串常量值”。相同值的字符串只会分配一次,后面的就会复用同一引用。
  • 通过 string.Intern(string) 方法主动添加驻留池。

string st1 = "123" + "abc";
string st2 = "123abc";
string st3 = st2.Substring(0,3);

看看上面代码生成的IL代码:

  • 常量的字符串 "123" + "abc" 连接被编译器优化了。
  • 常量字符串使用指令“ldstr”加载的到栈,该指令会先查看驻留池中是否已存在,如果已存在则直接返回已有字符串对象的地址,否则就加入。

驻留的字符串(字符串池)在托管堆上存储,大家共享,内部其实是一个哈希表,存储被驻留的字符串和其内存地址。驻留池生命周期同进程,并不受GC管理,因此无法被回收。因此需要注意:

  • lock 锁不能用string,避免使用同一个锁(字符串引用)。
  • 避免创建字面量的大字符串,会常住内存无法释放,当然也不要滥用 string.Intern(string) 方法。

2.3、字符串的查找、比较

string 的 比较字符串 是默认包含文化和区分大小写的顺序比较,C#内置的一个字符串比较规则(枚举) StringComparison ,可设置比较规则。在很多内置方法中使用,包括 String.Equals、String.Compare、String.IndexOf 和 String.StartsWith等。

? 微软官方建议在使用上述字符串比较方法中明确指定 StringComparison 参数值,而不是默认的比较规则。

public enum StringComparison
{
	CurrentCulture,
	CurrentCultureIgnoreCase,
	InvariantCulture,
	InvariantCultureIgnoreCase,
	Ordinal,
	OrdinalIgnoreCase
}
void Main()
{
	string.Equals("ABC","abc",StringComparison.Ordinal);           //Fasle
	string.Equals("ABC","abc",StringComparison.OrdinalIgnoreCase); //True
	string.Compare("ABC","abc",StringComparison.Ordinal);          //-32
	string.Compare("ABC","abc",StringComparison.OrdinalIgnoreCase);//0
}
枚举值 说明
CurrentCulture 本地语言区域规则,适用于给用户显示的内容
CurrentCultureIgnoreCase 同上+忽略大小写
InvariantCulture 固定语言区域,适用于存储的数据
InvariantCultureIgnoreCase 同上+忽略大小写
Ordinal 二进制值顺序比较字符串,比较快⚡
OrdinalIgnoreCase 同上+忽略大小写

如果单纯从性能角度考虑,考虑语言文化的字符串比较其实比较慢,来测试对比一下。测试代码:

string s1 = "hellohellohellohello";
string s2 = "helloHelloHelloHello";

public bool Equals() => s1.Equals(s2);//False

public bool Equals_CurrentCulture() => s1.Equals(s2,StringComparison.CurrentCulture);//False
public bool Equals_CurrentCultureIgnoreCase() => s1.Equals(s2,StringComparison.CurrentCultureIgnoreCase);//True
public bool Equals_InvariantCulture() => s1.Equals(s2,StringComparison.InvariantCulture);//False
public bool Equals_InvariantCultureIgnoreCase() => s1.Equals(s2,StringComparison.InvariantCultureIgnoreCase);//True
public bool Equals_Ordinal() => s1.Equals(s2,StringComparison.Ordinal);//False
public bool Equals_OrdinalIgnoreCase() => s1.Equals(s2,StringComparison.OrdinalIgnoreCase);//True

public bool Equals_Span() => s1.AsSpan() == s2.AsSpan();//False
  • 上面7个方法 分别测试了 Equals 的默认版本、及带参 StringComparison 的不同比较规则的性能。
  • 最后加了一个使用 Span 的相等比较,更多关于Span的资料查看《 高性能的Span、Memory 》。

?测结结论

  • Span 最快,其次无参 Equals() 版本、 Ordinal ,他们都是只比较二进制值,不考虑文化信息。
  • 个人理解,如果不考虑一些比较特别的语言(如瑞典语、土耳其语、 阿塞拜疆语等),只是针对英文、中文的字符串,一般不用考虑文化语义。
  • Equals() 默认是不考虑文化语义的字符值比较,但有些比较方法就不一定能了,比如 StartsWith Compare 默认的是带文化语义的 CurrentCulture 规则,因此推荐主动配置 StringComparison 参数。

2.4、字符串转义\

转义字符:反斜杠“\”

转义序列 字符名称 Unicode 编码
\' 单引号 0x0027
\" 双引号 0x0022
\0 null 0x0000
\b Backspace 0x0008
\f 换页 0x000C
\n 换行 0x000A
\r 回车 0x000D
\t 水平制表符 0x0009


03、?字符串连接的8种方式

字符串连接(组装)的使用是非常频繁的,.Net中提供了多种姿势来实现,各有特点。

连接方法 示例/说明
直接相加 "hello"+str ,其实编译后为 string.Concat ("hello", str)
连接函数:String.Concat() 字符串相加一般就是被编译为调用 String.Concat() 方法,有很多重载,支持任意多个参数
集合连接函数:String.Join() 将(集合)参数连接为一个字符串, string.Join('-',1,2,3); //1-2-3
格式化:String.Format() 传统的字符串格式化手艺, string.Format("name:{0},age:{1}",str,18)
$ 字符串插值 用花括号 {var} 引用变量、表达式,强大、方便, $"Hello {name} !"
@ 逐字文本字面量 支持转义符号、换行符,常用于文件路径、多行字符: @$"C:\\Users\\{name}\\Downloads"
""" 原始字符串字面量 C# 11,三个双冒号包围,支持多行文本的原始字面量。
StringBuilder 当处理大量字符串连接操作时,推荐使用 StringBuilder ,效果更优。

字面量字符串的相加会被编译器优化,直接合并为一个字符串。

var str1 = "Hello " + "world" + " !";
var str2 = DateTime.Now.Year + "年" + DateTime.Now.Month + "月";

//编译后的代码:
string str1 = "Hello world !";
string str2 = string.Concat (DateTime.Now.Year.ToString (), "年", DateTime.Now.Month.ToString (), "月");

3.1、字符串格式化 String.Format

String.Format 方法是早期比较常用的字符串组织方式,后来 $ 字符串插值 问世后就逐步被打入冷宫了。

string.Format("{0}+{1} = {2}",1,2,3);  //1+2 = 3
string.Format("Hello {0},{0}","sam");  //Hello sam,sam
String.Format("It is now {0:yyyy-MM-dd} at {0:hh:mm:ss}", DateTime.Now); //It is now 2024-01-17 at 10:56:33
String.Format("买了{0}个桔子,共花了{1:C2}。", 4,25.445); //买了4个桔子,共花了¥25.45。

基本语法规则就是用 {index} 来占位,在后面的参数中给出值。

  • 索引位置从0开始,必须连续递增,可以重复。
  • 索引的位置对应后面参数的顺序位置,必须对应,参数不能少(抛出异常),可以多。
  • 字符串格式规则参考后文《字符串格式总结》。

3.2、$字符串插值

字符串插值的格式: $"{}" ,大括号中可以是一个变量,一个(简单)表达式语句,还支持设置格式。功能强大、使用方便,老人孩子都爱用!

  • {} 字符转义,用两个 {{}} 即可,如果只有一边,则用单引号 '{{' ,即输出为 {
  • 使用三元运算符 ? 表达式,用括号包起来即可,因为“ : ”在插值字符串中有特殊含义,即格式化。
  • 字符串格式规则参考后文《字符串格式总结》。
var name = "sam";
Console.WriteLine($"Hello {name}!");  //Hello sam!
Console.WriteLine($"日期:{DateTime.Now.AddDays(1):yyyy-MM-dd HH:mm:ss}");  //日期:2024-01-18 23:21:55!
Console.WriteLine($"ThreadID:{Environment.CurrentManagedThreadId:0000}");  //ThreadID:0001
Console.WriteLine($"Length:{name.Length}");  //Length:3
Console.WriteLine($"Length:{(name.Length>3?"OK":"Error")}");  //Length:Error

3.3、@字符串支持任意字符

@ 标记的字符串为字面量字符串 ,不需要使用转义字符了,可搭配 $ 字符串插值使用。文件路径地址都会用到 @ ,两个冒号表示一个冒号, @"a""b" == a"b

var path= @"D:\GApp\LINQPad 8\x64";
var file = $@"D:\GApp\LINQPad 8\x64\{DateTime.Now:D}";
var maxText = @"Hi All:
	第一行
		换行
	";

3.4、??StringBuilder

StringBuilder 字符串修理工程师,顾名思义,就是专门用来组装字符串的,可以看做是一个可变长字符集合。适用于把很多字符串组装到一起的场景,避免了大量临时字符串对象的创建,可显著提升性能。

var sb = new StringBuilder(100);
sb.Append("sam");
sb[0] = 'F';  //Fam
sb.AppendLine("age");
sb.Append("age").Append(Environment.NewLine); //效果同上
sb.Insert(2,"---");
sb.Replace("age","Age");

var result = sb.ToString(); //获取结果
属性 特点/说明
Capacity 获取、设置字符容量(实际占用内存),默认16,当内容增多容量不足时,会自动扩容。
MaxCapacity 获取最大容量,20亿字符
Length 实际字符内容的长度,可赋值,设置 0 则清空已有字符内容,但并不影响 Capacity 。
Chars[Int32] 索引器,可获取、设置字符
?方法 特点/说明
StringBuilder(Int32) 构造函数,参数指定初始容量capacity
Append(value) 追加字符,很多重载版本,类似还有AppendFormat、AppendJoin
AppendLine 追加字符后,再追加一个换行符
Insert (int index, value) 指定位置插入字符内容
Replace(Char, Char) 查找替换字符(字符串)内容,会替换所有找到的字符内容
ToString() 将 StringBuilder 输出为一个字符串,一般是 StringBuilder 的命运终点。
  • 各种 Append 方法都返回自身,可用来链式编程。
  • StringBuilder 默认容量为16,内部有一个 char 数组 m_ChunkChars (缓冲区)来存储字符内容,如下 StringBuilder 构造函数 源码 :
public StringBuilder()
{
	m_MaxCapacity = int.MaxValue;
	m_ChunkChars = new char[16];
}
  • 当不断追加字符串,容量不足会自动扩容,扩容的过程其实就是创建更大的字符数组(容量翻倍),把原来的值拷贝过来,这个过程会涉及数组对象创建、内存拷贝。

? 一般使用 StringBuilder 建议尽量给一个合理的默认容量大小,尽量避免、减少频繁的扩容。


04、?字符串格式化大全

?字符串格式语法: {index/interpolationExpression [,alignment][:formatString]}

  • ,alignment 可选,设置字符串的对齐长度,如果位数不够则空格补齐,正数部补左边,负数补右边。
  • :formatString 指定格式规则。一次只能指定一个格式规则,可和 ,alignment 共存。
//,alignment 示例
var name = "sam";
$"name:{name,6}.";    //字符长度6,前面补齐空格 //name:   sam.
$"name:{name,-6}.";   //字符长度6,后面补齐空格 //name:sam   .
"1123+1 = {(1223+1),6:#,#.##}";                //1123+1 =  1,224
string.Format("1123+1 = {0,6:#,#.##}",1223+1); //1123+1 =  1,224

4.1、 数值格式

?标准数值格式

?数值格式 说明
E3/e3 科学计数法(指数),数字"3"为小数精度, $"{12345.2:E3}" //1.235E+004 , E+4 表示10的4次方;如果是 E-4 则表示为小数(除以10的四次方) 1E-4 = 0.0001
F4 定点格式,小数精度为"4",位数不够后面补0,支持所有数值类型, $"{123.22F:F4}" //123.2200
G4 定点格式 F +指数 E 的结合版,最多"4"个有效数字,超过就用科学计数法。 "{123:G2}" //1.2E+02 , $"{123:G4}" //123
C3 货币格式(支持千分位),数字“3”为小数位数, $"{123.346:C2}" //¥123.35
P2 百分比格式,数字乘以100后转换为百分数,数字“2”为小数位数, $"{0.2:P2}" //20.00 %
N6 数字格式化(支持千分位),小数位数为6,不够后面补0, $"{123:N6}" //123.000000
D6 整数定长格式,不够前面补0,只支持整数, $"{123:D6}" //000123
B 输出为二进制格式,仅支持整数+.Net8,精度为字符串位数,不够补0, $"{123:B}" //1111011
X/x 输出为十六进制格式,仅支持整数+,精度为字符串位数,不够补0, $"{12:X4}" //000C

?自定义的数值格式

?数值格式符号 说明
# 数字占位符,不强制占位, $"{123:#,###.##}" //123
0 数字(0)占位符,强制占位,不够补0。 $"{123:0000.00}" //0123.00
. 小数点,
, 千分位,
, 倍数符号,也是逗号,在末尾、小数点前为倍数符号,除以1000,可多个。 $"{12000:#,}" //12
% 百分数,乘一百+%, $"{0.2:00.00%}" //20.00 %
E / e 指数(科学计数), $"{10.1234:0.00e0}" //1.01e1 ; $"{0.01234:0.00e0}" //1.23e-2
\\ 转义字符,

?热知识 :小数格式化截断时都会四舍五入,(int)double 强转换是直接截断整数部分,相当于向下取整。
?冷知识 :土耳其文化中的小数点为“逗号”,而非“点”。

4.2、日期时间格式

?日期格式-自定义 说明( DateTime DateTimeOffset
yyyy 年份, yyyy //2024, yy //24
MM 2位数的月份,1个M就不会补0 了,3/4个M为月份名称。 M //4, MM //04, MMM //4月, MMMM //四月
dd 2位数的日,3/4个d为星期。 d //8, dd //08, ddd //周一, dddd //星期一
HH 2位数的小时(24小时制)
hh 2位数的小时(12小时制)
mm 2位数的分钟
ss 2位数的秒
f 为1/10秒单位, ff 为1/100秒单位,以此类推, fff 就表示毫秒
tt AM/PM 指示符
组合使用 以上可组合使用,可穿插任意字符, $"{DateTime.Now:yyyy年MM月dd日 HH:mm:ss}"
?日期格式-简写 说明
D、d D 长日期, d 短日期, $"{DateTime.Now:D}" //2024年1月18日
F、f 完整日期/时间模式, F 长时间, f 短时间, $"{DateTime.Now:F}" //2024年1月18日 22:45:34
T、t T 长时间, t 短时间, $"{DateTime.Now:T}" //22:45:42
M/m 月日模式, $"{DateTime.Now:M}" //1月18日
Y/y 年月模式, $"{DateTime.Now:Y}" //2024年1月

4.3、其他格式

?枚举格式 说明
G/g,F/f 枚举的字符串名称,其中F用于Flags, $"{UType.User:G}" //User
D/d 十进制枚举值, $"{UType.User:D}" //2
X/x 十六进制枚举值, $"{UType.User:X}" //00000002
?其他 说明
IFormattable 自定义的格式化接口,使用自定义的 IFormatProvider 来实现格式化输出ToString()
NumberStyles 用于解析数字符串(Parse)时指定的解析格式
DateTimeStyles 同上,用于时间日期的解析

?格式MSDN参考资料

  • 所有整型和浮点类型。 (请参阅 标准数字格式字符串 和 自定义数值格式字符串 。)
  • DateTime 和 DateTimeOffset 。 (请参阅 标准日期和时间格式字符串 和 自定义日期和时间格式字符串 。)
  • 所有枚举类型。 (请参阅 枚举格式字符串 .)
  • TimeSpan 值。 (请参阅 标准 TimeSpan 格式字符串 和 自定义 TimeSpan 格式字符串。 )
  • GUID。 (请参阅 Guid.ToString(String) 方法。)

06、高性能字符串实践

提高string处理性能的核心就是: 尽量减少临时字符串对象的创建

  • 高频常用字符串(非字面量)可考虑主动驻留字符串, string.Intern(name)
  • 字符串的比较、查找,优先用Span,或者尽量使用无文化语义的比较 StringComparison.Ordinal
  • 大量字符串连接使用StringBuilder,且尽量给定一个合适的容量大小,避免频繁的扩容。
  • 少量字符串连接用字符串插值即可,创建StringBuilder也是有成本的。
  • 如果有大量StringBuilder 的使用,可以考虑用StringBuilderCache,或池化StringBuilder。

6.1、比较字符串

  • 字符串查找、拆分字符串、解析字符串,推荐使用Span,参考《 高性能的Span、Memory 》。
  • 查找、比较字符串,尽量指定 StringComparison Ordinal OrdinalIgnoreCase ,采用无文化特征的比较性能更快。
string str1="a",str2 = "b";
//这种方式会产生新的字符串,不推荐
if(str1.ToLower() == str2.ToLower()){} 
//推荐写法
if(string.Compare(str1, str2, true)==0){} 
if(string.Equals(str1,str2,StringComparison.Ordinal)){}

6.2、字符串真的不能修改吗?

字符串其实也是可以修改的,当然是用非常规手段。

  • ref 获取指定字符的引用地址(指针地址)。
static void Main(string[] args)
{
	var str1 = "hello";
    var str2 = "hello";
	//修改第0位
    ref var c1 = ref MemoryMarshal.GetReference(str1);
	c1 = 'H';
	//修改第一位
	ref var c2 = ref MemoryMarshal.GetReference(str1.AsSpan(1));
	c2 = 'E';
	Console.WriteLine(str1);//输出:HEllo
	Console.WriteLine(str2);//输出:HEllo
}
  • 直接使用指针修改字符值。
void Main()
{
	var str1 = "hello";
	var str2 = "hello";
	unsafe
	{
		fixed (char* c = str2)
		{
			c[0] = 'H';
			c[1] = 'E';
		}
	}
	Console.WriteLine(str1); //HEllo
	Console.WriteLine(str2); //HEllo
}

参考资料

  • C# 文档
  • 《C#8.0 In a Nutshell》
  • .NET面试题解析(03)-string与字符串操作
  • .NET 中的字符编码

©️版权申明 :版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处! 原文编辑地址-语雀