C#判断字符串是否为空的三种方法及其比较

时间:2020-12-26 19:33:38

1.最普遍、最直接的方法

  1.             string Input = "Yours Input Msg";
  2.             if (Input == "")
  3.                 ;

IL:

  1.     .locals init ([0] string Input,[1] bool CS$4$0000)
  2.     IL_0000:  nop
  3.     IL_0001:  ldstr      "Yours Input Msg"
  4.     IL_0006:  stloc.0
  5.     IL_0007:  ldloc.0
  6.     IL_0008:  ldstr      ""
  7.     IL_000d:  call       bool [mscorlib]System.String::op_Equality(string,string)

 

2.使用String.Empty

  1.             string Input = "Yours Input Msg";
  2.             if (Input == String.Empty)
  3.                 ;

IL:

  1.     .locals init ([0] string Input,[1] bool CS$4$0000)
  2.     IL_0000:  nop
  3.     IL_0001:  ldstr      "Yours Input Msg"
  4.     IL_0006:  stloc.0
  5.     IL_0007:  ldloc.0
  6.     IL_0008:  ldsfld     string [mscorlib]System.String::Empty
  7.     IL_000d:  call       bool [mscorlib]System.String::op_Equality(string,string)

3.使用string.Length

  1.             string Input = "Yours Input Msg";
  2.             if (Input.Length == 0)
  3.                 ;

 

IL:

  1.     .locals init ([0] string Input,[1] bool CS$4$0000)
  2.     IL_0000:  nop
  3.     IL_0001:  ldstr      "Yours Input Msg"
  4.     IL_0006:  stloc.0
  5.     IL_0007:  ldloc.0
  6.     IL_0008:  callvirt   instance int32 [mscorlib]System.String::get_Length()

在看过了三种方法之后,我们从底层开始进行比较,这里先做个假设,我们假设所有的单个IL语句执行时间相等,单位为1。(其实还是有差异的,但并不影响我们作比较。)

 

我们先反汇编mscorlib.dll得到System.String::op_Equality(strin,string):

  1.   .method public hidebysig specialname static 
  2.           bool  op_Equality(string a,
  3.                             string b) cil managed
  4.   {
  5.     // 代码大小       8 (0x8)
  6.     .maxstack  8
  7.     IL_0000:  ldarg.0
  8.     IL_0001:  ldarg.1
  9.     IL_0002:  call       bool System.String::Equals(string,
  10.                                                     string)
  11.     IL_0007:  ret
  12.   } // end of method String::op_Equality

发现他还要调用这个函数System.String::Equals(strin,string):

  1.   .method public hidebysig virtual instance bool 
  2.           Equals(object obj) cil managed
  3.   {
  4.     .custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency,
  5.                                                                                                   valuetype System.Runtime.ConstrainedExecution.Cer) = ( 01 00 03 00 00 00 01 00 00 00 00 00 ) 
  6.     // 代码大小       23 (0x17)
  7.     .maxstack  2
  8.     .locals init (string V_0)
  9.     IL_0000:  ldarg.1
  10.     IL_0001:  isinst     System.String
  11.     IL_0006:  stloc.0
  12.     IL_0007:  ldloc.0
  13.     IL_0008:  brtrue.s   IL_000f
  14.     IL_000a:  ldarg.0
  15.     IL_000b:  brfalse.s  IL_000f
  16.     IL_000d:  ldc.i4.0
  17.     IL_000e:  ret
  18.     IL_000f:  ldarg.0
  19.     IL_0010:  ldloc.0
  20.     IL_0011:  call       bool System.String::EqualsHelper(string,
  21.                                                           string)
  22.     IL_0016:  ret
  23.   } // end of method String::Equals

然后还要调用System.String::EqualsHelper(string,string):

  1.   .method private hidebysig static bool  EqualsHelper(string strA,
  2.                                                       string strB) cil managed
  3.   {
  4.     .custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency,
  5.                                                                                                   valuetype System.Runtime.ConstrainedExecution.Cer) = ( 01 00 03 00 00 00 01 00 00 00 00 00 ) 
  6.     // 代码大小       187 (0xbb)
  7.     .maxstack  3
  8.     .locals init (int32 V_0,
  9.              char* V_1,
  10.              char* V_2,
  11.              char* V_3,
  12.              char* V_4,
  13.              bool V_5,
  14.              string pinned V_6,
  15.              string pinned V_7)
  16.     IL_0000:  ldarg.0
  17.     IL_0001:  callvirt   instance int32 System.String::get_Length()
  18.     IL_0006:  stloc.0
  19.     IL_0007:  ldloc.0
  20.     IL_0008:  ldarg.1
  21.     IL_0009:  callvirt   instance int32 System.String::get_Length()
  22.     IL_000e:  beq.s      IL_0012
  23.     IL_0010:  ldc.i4.0
  24.     IL_0011:  ret
  25.     IL_0012:  ldarg.0
  26.     IL_0013:  stloc.s    V_6
  27.     IL_0015:  ldloc.s    V_6
  28.     IL_0017:  conv.i
  29.     IL_0018:  dup
  30.     IL_0019:  brfalse.s  IL_0021
  31.     IL_001b:  call       int32 System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
  32.     IL_0020:  add
  33.     IL_0021:  stloc.1
  34.     IL_0022:  ldarg.1
  35.     IL_0023:  stloc.s    V_7
  36.     IL_0025:  ldloc.s    V_7
  37.     IL_0027:  conv.i
  38.     IL_0028:  dup
  39.     IL_0029:  brfalse.s  IL_0031
  40.     IL_002b:  call       int32 System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
  41.     IL_0030:  add
  42.     IL_0031:  stloc.2
  43.     IL_0032:  ldloc.1
  44.     IL_0033:  stloc.3
  45.     IL_0034:  ldloc.2
  46.     IL_0035:  stloc.s    V_4
  47.     IL_0037:  br.s       IL_008b
  48.     IL_0039:  ldloc.3
  49.     IL_003a:  ldind.i4
  50.     IL_003b:  ldloc.s    V_4
  51.     IL_003d:  ldind.i4
  52.     IL_003e:  bne.un.s   IL_00a9
  53.     IL_0040:  ldloc.3
  54.     IL_0041:  ldc.i4.4
  55.     IL_0042:  conv.i
  56.     IL_0043:  add
  57.     IL_0044:  ldind.i4
  58.     IL_0045:  ldloc.s    V_4
  59.     IL_0047:  ldc.i4.4
  60.     IL_0048:  conv.i
  61.     IL_0049:  add
  62.     IL_004a:  ldind.i4
  63.     IL_004b:  bne.un.s   IL_00a9
  64.     IL_004d:  ldloc.3
  65.     IL_004e:  ldc.i4.8
  66.     IL_004f:  conv.i
  67.     IL_0050:  add
  68.     IL_0051:  ldind.i4
  69.     IL_0052:  ldloc.s    V_4
  70.     IL_0054:  ldc.i4.8
  71.     IL_0055:  conv.i
  72.     IL_0056:  add
  73.     IL_0057:  ldind.i4
  74.     IL_0058:  bne.un.s   IL_00a9
  75.     IL_005a:  ldloc.3
  76.     IL_005b:  ldc.i4.s   12
  77.     IL_005d:  conv.i
  78.     IL_005e:  add
  79.     IL_005f:  ldind.i4
  80.     IL_0060:  ldloc.s    V_4
  81.     IL_0062:  ldc.i4.s   12
  82.     IL_0064:  conv.i
  83.     IL_0065:  add
  84.     IL_0066:  ldind.i4
  85.     IL_0067:  bne.un.s   IL_00a9
  86.     IL_0069:  ldloc.3
  87.     IL_006a:  ldc.i4.s   16
  88.     IL_006c:  conv.i
  89.     IL_006d:  add
  90.     IL_006e:  ldind.i4
  91.     IL_006f:  ldloc.s    V_4
  92.     IL_0071:  ldc.i4.s   16
  93.     IL_0073:  conv.i
  94.     IL_0074:  add
  95.     IL_0075:  ldind.i4
  96.     IL_0076:  bne.un.s   IL_00a9
  97.     IL_0078:  ldloc.3
  98.     IL_0079:  ldc.i4.s   20
  99.     IL_007b:  conv.i
  100.     IL_007c:  add
  101.     IL_007d:  stloc.3
  102.     IL_007e:  ldloc.s    V_4
  103.     IL_0080:  ldc.i4.s   20
  104.     IL_0082:  conv.i
  105.     IL_0083:  add
  106.     IL_0084:  stloc.s    V_4
  107.     IL_0086:  ldloc.0
  108.     IL_0087:  ldc.i4.s   10
  109.     IL_0089:  sub
  110.     IL_008a:  stloc.0
  111.     IL_008b:  ldloc.0
  112.     IL_008c:  ldc.i4.s   10
  113.     IL_008e:  bge.s      IL_0039
  114.     IL_0090:  br.s       IL_00a9
  115.     IL_0092:  ldloc.3
  116.     IL_0093:  ldind.i4
  117.     IL_0094:  ldloc.s    V_4
  118.     IL_0096:  ldind.i4
  119.     IL_0097:  bne.un.s   IL_00ad
  120.     IL_0099:  ldloc.3
  121.     IL_009a:  ldc.i4.4
  122.     IL_009b:  conv.i
  123.     IL_009c:  add
  124.     IL_009d:  stloc.3
  125.     IL_009e:  ldloc.s    V_4
  126.     IL_00a0:  ldc.i4.4
  127.     IL_00a1:  conv.i
  128.     IL_00a2:  add
  129.     IL_00a3:  stloc.s    V_4
  130.     IL_00a5:  ldloc.0
  131.     IL_00a6:  ldc.i4.2
  132.     IL_00a7:  sub
  133.     IL_00a8:  stloc.0
  134.     IL_00a9:  ldloc.0
  135.     IL_00aa:  ldc.i4.0
  136.     IL_00ab:  bgt.s      IL_0092
  137.     IL_00ad:  ldloc.0
  138.     IL_00ae:  ldc.i4.0
  139.     IL_00af:  cgt
  140.     IL_00b1:  ldc.i4.0
  141.     IL_00b2:  ceq
  142.     IL_00b4:  stloc.s    V_5
  143.     IL_00b6:  leave.s    IL_00b8
  144.     IL_00b8:  ldloc.s    V_5
  145.     IL_00ba:  ret
  146.   } // end of method String::EqualsHelper

粗略估算了下,System.String::op_Equality(strin,string)执行时间大概是160。

这样方法1的大概运行时间是165,空间大概是220字节。方法2比只多了一次调用System.String::Empty(程序中的静态变量)

  1.  .field public static initonly string Empty

因此运行运行时间基本同方法1一致,空间上节省了2字节(原因是方法1需要先建立一个string类型("")后,再进行判断),可以忽略不计。很多文章谈到C#判断空字符串不同方法性能的时候往往以此为据,而看过上面的IL之后,我却不这么认为,真正的性能差异体现在了System.String::op_Equality(string,string)和System.String::get_Length()这两个函数之上。

下面我们看一下System.String::get_Length():

  1.   .method public hidebysig specialname instance int32 
  2.           get_Length() cil managed internalcall
  3.   {
  4.   } // end of method String::get_Length

这个函数的函数体居然是空的?为什么?那难道这个不起作用吗?我认为是cil managed internalcall这个起了作用,取得字符串(内存中一块连续的区域)长度是非常快的,可以由CPU直接完成(这个是我的猜测,目前关于internalcall我们仅仅知道internalcall在RuntimeType类里面则是很容易就能够找到,速度很快,性能很高,这个可能涉及到程序的编译原理,不懂~~~)

反正这个函数很快很快就是了,那么方法3的运行时间大概只有6,空间大概是20多。

这样,我们得到了一个令人吃惊的结论,方法3在时间上只有前两种方法的约1/28,空间约是1/9。

为什么这样,我也不明白,我希望高手来指出我叙述或者论证不当的地方。

 

 

 

那么我们是否可以就直接使用方法3了呢?

答案是否定的。在C#使用属性时一定要注意异常捕捉的问题。

String的Length属性返回此实例中Char对象的个数,在C#中字符串可以看成是由多个Char组成的字符数组,这样如果String为null的话问题就来了,当String置为null时,相当于一个没有实例化的字符串数组,用Length取一个没有实例化数组的长度,那么产生异常就在所难免了。

 

因此应该这么做:

  1.             string Input = "Yours Input Msg";
  2.             if (Input!=null && Input.Length==0)
  3.                 ;

 

看一下IL:

 

  1.     .locals init ([0] string Input,[1] bool CS$4$0000)
  2.     IL_0000:  nop
  3.     IL_0001:  ldstr      "Yours Input Msg"
  4.     IL_0006:  stloc.0
  5.     IL_0007:  ldloc.0
  6.     IL_0008:  brfalse.s  IL_0018
  7.     IL_000a:  ldloc.0
  8.     IL_000b:  callvirt   instance int32 [mscorlib]System.String::get_Length()
  9.     IL_0010:  ldc.i4.0
  10.     IL_0011:  ceq
  11.     IL_0013:  ldc.i4.0
  12.     IL_0014:  ceq
  13.     IL_0016:  br.s       IL_0019

运行时间大概是在5-13,空间越是30字节。这样,比起Input == ""这个方法,效率大约提高

13-30多倍(因为有短路运算),空间却只要1/7。

 

因此这些方法中判断字符串是否为空的最佳方法是:

  1. desStr!=null && desStr.Length==0

 

 

本人初涉C#,很多东西掌握的都不是很好,如果你有自己的看法,请告知我,一起学习,一起进步:)