笔记:改善C#程序建议1:正确操作字符串

改善C#程序建议1:正确操作字符串

1.确保尽量少的装箱
2.避免分配额外的内存空间

第一个方面:

示例代码片

代码片

private void Test1()
{
	string str1 = "test" + 1;
	string str2 = "test" + 2.ToString();
}

IL代码片

.method private hidebysig instance void  Test1() cil managed
{
  // 代码大小       39 (0x27)
  .maxstack  2
  .locals init (string V_0,
           string V_1,
           int32 V_2)
  IL_0000:  nop
  //第一行代码对应的LL代码如下:
  IL_0001:  ldstr      "test"
  IL_0006:  ldc.i4.1
  IL_0007:  box        [System.Runtime]System.Int32
  IL_000c:  call       string [System.Runtime]System.String::Concat(object,
                                                                    object)
  IL_0011:  stloc.0
  //第二行代码对应的I代码如下:
  IL_0012:  ldstr      "test"
  IL_0017:  ldc.i4.2
  IL_0018:  stloc.2
  IL_0019:  ldloca.s   V_2
  IL_001b:  call       instance string [System.Runtime]System.Int32::ToString()
  IL_0020:  call       string [System.Runtime]System.String::Concat(string,
                                                                    string)
  IL_0025:  stloc.1
  IL_0026:  ret
} // end of method Program::Test1

通过比较 IL代码,发现第一行代码 string str1 = "test" + 1; 发生装箱操作,而第二行代码却没有装箱行为,所以在使用其他值类型到字符转换拼接时,避免直接使用 “+” 和值类型拼接,应该使用值类型提供的ToString方法拼接。


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

注意:装箱之所以会带来性能损耗,因为它需要完成下面三个步骤:
	1.首先,会为值类型在托管堆中分配内存。除子值类型所分配的内存外,内存总量还要加上类型对象指针和同步块索引所占用的内存。
	2.将值类型的值复制到新分配的内存中。
	3.返回已经成为引用类型的对象的地址。

第二个方面:

避免分配额外的内在空间。对 CLR 来说,string 对象(字符串对象)是个很特殊的对象,它一旦被赋值就不可改变。在运行时调用 System.String 类中的任何方法或进行任何运算(如“=”、“+”等)操作,都会在内存中创建一个新的字符串对象,这也意味要为该对象分配新的内存空间。

1、下面的代码就会带来运行时的额外开销。

代码片

private void Test2()
{
	string str1 = "123";
	str1 = "Test2" + str1 + "456";
	//以上两行代码会生成3个字符串对象,并执行了一次 string.Contact 方法
}

IL代码片

.method private hidebysig instance void  Test2() cil managed
{
  // 代码大小       25 (0x19)
  .maxstack  3
  .locals init (string V_0)
  IL_0000:  nop
  IL_0001:  ldstr      "123"
  IL_0006:  stloc.0
  IL_0007:  ldstr      "Test2"
  IL_000c:  ldloc.0
  IL_000d:  ldstr      "456"
  IL_0012:  call       string [System.Runtime]System.String::Concat(string,
                                                                    string,
                                                                    string)
  IL_0017:  stloc.0
  IL_0018:  ret
} // end of method Program::Test2

2、下面的代码不会在运行时拼接字符串,而是会在编译时直接生成字符串

private void Test3()
{
	string str1 = "123" + "456" + "Test3"; 
	//等效于 string str1 = "123456Test3";
}
private void Test4()
{
	const string a = "Test4";
	string str1 = "123" + a;
	//因为a是一个常量,所以 等效于 string str1 = "123" + "Test4";
	//最终等效于 string str1 = "123Test4";
}

IL代码片

.method private hidebysig instance void  Test3() cil managed
{
  // 代码大小       8 (0x8)
  .maxstack  1
  .locals init (string V_0)
  IL_0000:  nop
  IL_0001:  ldstr      "123456Test3"
  IL_0006:  stloc.0
  IL_0007:  ret
} // end of method Program::Test3

.method private hidebysig instance void  Test4() cil managed
{
  // 代码大小       8 (0x8)
  .maxstack  1
  .locals init (string V_0)
  IL_0000:  nop
  IL_0001:  ldstr      "123Test4"
  IL_0006:  stloc.0
  IL_0007:  ret
} // end of method Program::Test4

由于使用 System.String类会在某些场合带来明显的性能损耗,所以微软另外提供了一个类型 StringBuilder来弥补 String 的不足。StringBuilder并不会重新创建一个 string对象,它的效率源于预先以非托管的方式分配内存。
如果 StringBuilder 没有先定义长度,则默认分配的长度为16当 StringBuilder 宇符长度小于等于16时, StringBuilder 不会重新分配内存;当StringBuilder 字符长度大于16小于32时, StringBuilder 又会重新分配内存,使之成为16的倍数。
在上面的代码中,如果预先判断字符串的长度将大于16,则可以为其设定一个更加合适的长度(如32),String Builder重新分配内存时是按照上次的容量加倍进行分配的。当然,我们需要注意,StringBuilder指定的长度要合适太小了,需要频繁分配内存:太大了,浪费空间。

微软还提供了另外一个方法来简化这种操作,即使用 string.Format方法。 stringFormat方法在内部使用 String Builder进行字符串的格式化,如下面的代码所示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值