C# 正确操作字符串,规避字符串转换所带来的额外开销

一、确保尽量少的装箱

Microsoft Visual Studio Community 2019
版本 16.6.2
VisualStudio.16.Release/16.6.2+30204.135
Microsoft .NET Framework
版本 4.8.03752

(此版本下编译是string str1 = "str1" + 9;会变为string str2 = "str2" + 9.ToString();

即不加ToString方法也不会装箱。

在自己编写的代码中,应当尽可能地避免编写不必要的装箱代码

1 string str1 = "str1" + 9;//装箱(vs2010版本,.net framework 4.0)
2 string str2 = "str2" + 9.ToString();

第一行代码对应的IL代码:

.maxstack 2
    .entrypoint
    .locals init (
        [0] string str1
    )

    IL_0000: nop
    IL_0001: ldstr     "abc"
    IL_0006: ldc.i4.s  9
    IL_0008: box       [mscorlib]System.Int32
    IL_000D: call      string [mscorlib]System.String::Concat(object, object)
    IL_0012: stloc.0
    IL_0013: ret

在运行时会完成一次装箱行为box

第二行代码对应的IL代码:

.maxstack 2
    .entrypoint
    .locals init (
        [0] string str2,
        [1] int32
    )

    IL_0000: nop
    IL_0001: ldstr     "str2"
    IL_0006: ldc.i4.s  9
    IL_0008: stloc.1
    IL_0009: ldloca.s  V_1
    IL_000B: call      instance string [mscorlib]System.Int32::ToString()
    IL_0010: call      string [mscorlib]System.String::Concat(string, string)
    IL_0015: stloc.0
    IL_0016: ret

没有装箱行为,通过直接操作内存来完成从int到string的转换,效率比装箱高很多。

装箱需要完成一下三个步骤:

1、为值类型在托管堆中分配内存。除了值类型本身所分配的内存外,内存总量还要加上类型对象指针和同步块索引所占用的内存。

2、将值类型的值复制到新分配的堆内存中。

3、返回已经成为引用类型的对象的地址。

二、避免分配额外的内存空间

对于CLR,string对象一旦被赋值就不可改变,运行时调用System.String类中的任何方法或进行任何运算(如“=”赋值、“+”拼接等),

都会在内存中创建一个新的字符串对象,所以要为新的对象分配新的内存空间,带来运行时的额外开销。

string a = "a";
string b = "b";
string c = "c";
string d = "d";

a += b;//调用一次string.Contact方法
a += c;
a += d;

string str = a + b + c + d;//创建了4个字符串对象,调用一次string.Contact方法

string str1 = 9 + "abc";//发生一次装箱,调用一次string.Contact方法

string str2 = "123" +"abc" + "456";//该代码等效于 string str2 = "123abc456";

const string mConst = "c";
string str3 = "abc" + mConst;//因为mConst是常量,所以该代码等效于string str3="abc" + "c"; 最终等效于string str3=“abcc”;

微软提供了StringBuilder来弥补String的不足,它会预先以非托管的方式分配内存。

   StringBuilder str4 = new StringBuilder();//默认分配的长度为16
str4默认分配的长度为16,当str4的字符长度小于等于16时,不会重新分配内存;
当字符长度大于16小于32时,StringBuilder会重新分配长度32(按照上次的容量加倍进行分配)。
可以指定分配长度,要注意分配合理,太小了需要频繁分配内存;太大了浪费空间。
string a = "a";
string b = "b";
string c = "c";
string d = "d";

StringBuilder str5 = new StringBuilder(a);
str1.Append(b);//字符串的拼接
str1.Append(c);
str1.Append(d);

string str6 = string.Format("{0}{1}{2}{3}", a, b, c, d);//也可以使用string.Format方法在内部使用StringBuilder进行字符串格式化

参考:《编写高质量代码改善C#程序的157个建议》陆敏技

原文地址:https://www.cnblogs.com/xuyouyou/p/13143973.html