1.最普遍、最直接的方法
- string Input = "Yours Input Msg";
- if (Input == "")
- ;
IL:
- .locals init ([0] string Input,[1] bool CS$4$0000)
- IL_0000: nop
- IL_0001: ldstr "Yours Input Msg"
- IL_0006: stloc.0
- IL_0007: ldloc.0
- IL_0008: ldstr ""
- IL_000d: call bool [mscorlib]System.String::op_Equality(string,string)
2.使用String.Empty
- string Input = "Yours Input Msg";
- if (Input == String.Empty)
- ;
IL:
- .locals init ([0] string Input,[1] bool CS$4$0000)
- IL_0000: nop
- IL_0001: ldstr "Yours Input Msg"
- IL_0006: stloc.0
- IL_0007: ldloc.0
- IL_0008: ldsfld string [mscorlib]System.String::Empty
- IL_000d: call bool [mscorlib]System.String::op_Equality(string,string)
3.使用string.Length
- string Input = "Yours Input Msg";
- if (Input.Length == 0)
- ;
IL:
- .locals init ([0] string Input,[1] bool CS$4$0000)
- IL_0000: nop
- IL_0001: ldstr "Yours Input Msg"
- IL_0006: stloc.0
- IL_0007: ldloc.0
- IL_0008: callvirt instance int32 [mscorlib]System.String::get_Length()
在看过了三种方法之后,我们从底层开始进行比较,这里先做个假设,我们假设所有的单个IL语句执行时间相等,单位为1。(其实还是有差异的,但并不影响我们作比较。)
我们先反汇编mscorlib.dll得到System.String::op_Equality(strin,string):
- .method public hidebysig specialname static
- bool op_Equality(string a,
- string b) cil managed
- {
- // 代码大小 8 (0x8)
- .maxstack 8
- IL_0000: ldarg.0
- IL_0001: ldarg.1
- IL_0002: call bool System.String::Equals(string,
- string)
- IL_0007: ret
- } // end of method String::op_Equality
发现他还要调用这个函数System.String::Equals(strin,string):
- .method public hidebysig virtual instance bool
- Equals(object obj) cil managed
- {
- .custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency,
- valuetype System.Runtime.ConstrainedExecution.Cer) = ( 01 00 03 00 00 00 01 00 00 00 00 00 )
- // 代码大小 23 (0x17)
- .maxstack 2
- .locals init (string V_0)
- IL_0000: ldarg.1
- IL_0001: isinst System.String
- IL_0006: stloc.0
- IL_0007: ldloc.0
- IL_0008: brtrue.s IL_000f
- IL_000a: ldarg.0
- IL_000b: brfalse.s IL_000f
- IL_000d: ldc.i4.0
- IL_000e: ret
- IL_000f: ldarg.0
- IL_0010: ldloc.0
- IL_0011: call bool System.String::EqualsHelper(string,
- string)
- IL_0016: ret
- } // end of method String::Equals
然后还要调用System.String::EqualsHelper(string,string):
- .method private hidebysig static bool EqualsHelper(string strA,
- string strB) cil managed
- {
- .custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency,
- valuetype System.Runtime.ConstrainedExecution.Cer) = ( 01 00 03 00 00 00 01 00 00 00 00 00 )
- // 代码大小 187 (0xbb)
- .maxstack 3
- .locals init (int32 V_0,
- char* V_1,
- char* V_2,
- char* V_3,
- char* V_4,
- bool V_5,
- string pinned V_6,
- string pinned V_7)
- IL_0000: ldarg.0
- IL_0001: callvirt instance int32 System.String::get_Length()
- IL_0006: stloc.0
- IL_0007: ldloc.0
- IL_0008: ldarg.1
- IL_0009: callvirt instance int32 System.String::get_Length()
- IL_000e: beq.s IL_0012
- IL_0010: ldc.i4.0
- IL_0011: ret
- IL_0012: ldarg.0
- IL_0013: stloc.s V_6
- IL_0015: ldloc.s V_6
- IL_0017: conv.i
- IL_0018: dup
- IL_0019: brfalse.s IL_0021
- IL_001b: call int32 System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
- IL_0020: add
- IL_0021: stloc.1
- IL_0022: ldarg.1
- IL_0023: stloc.s V_7
- IL_0025: ldloc.s V_7
- IL_0027: conv.i
- IL_0028: dup
- IL_0029: brfalse.s IL_0031
- IL_002b: call int32 System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
- IL_0030: add
- IL_0031: stloc.2
- IL_0032: ldloc.1
- IL_0033: stloc.3
- IL_0034: ldloc.2
- IL_0035: stloc.s V_4
- IL_0037: br.s IL_008b
- IL_0039: ldloc.3
- IL_003a: ldind.i4
- IL_003b: ldloc.s V_4
- IL_003d: ldind.i4
- IL_003e: bne.un.s IL_00a9
- IL_0040: ldloc.3
- IL_0041: ldc.i4.4
- IL_0042: conv.i
- IL_0043: add
- IL_0044: ldind.i4
- IL_0045: ldloc.s V_4
- IL_0047: ldc.i4.4
- IL_0048: conv.i
- IL_0049: add
- IL_004a: ldind.i4
- IL_004b: bne.un.s IL_00a9
- IL_004d: ldloc.3
- IL_004e: ldc.i4.8
- IL_004f: conv.i
- IL_0050: add
- IL_0051: ldind.i4
- IL_0052: ldloc.s V_4
- IL_0054: ldc.i4.8
- IL_0055: conv.i
- IL_0056: add
- IL_0057: ldind.i4
- IL_0058: bne.un.s IL_00a9
- IL_005a: ldloc.3
- IL_005b: ldc.i4.s 12
- IL_005d: conv.i
- IL_005e: add
- IL_005f: ldind.i4
- IL_0060: ldloc.s V_4
- IL_0062: ldc.i4.s 12
- IL_0064: conv.i
- IL_0065: add
- IL_0066: ldind.i4
- IL_0067: bne.un.s IL_00a9
- IL_0069: ldloc.3
- IL_006a: ldc.i4.s 16
- IL_006c: conv.i
- IL_006d: add
- IL_006e: ldind.i4
- IL_006f: ldloc.s V_4
- IL_0071: ldc.i4.s 16
- IL_0073: conv.i
- IL_0074: add
- IL_0075: ldind.i4
- IL_0076: bne.un.s IL_00a9
- IL_0078: ldloc.3
- IL_0079: ldc.i4.s 20
- IL_007b: conv.i
- IL_007c: add
- IL_007d: stloc.3
- IL_007e: ldloc.s V_4
- IL_0080: ldc.i4.s 20
- IL_0082: conv.i
- IL_0083: add
- IL_0084: stloc.s V_4
- IL_0086: ldloc.0
- IL_0087: ldc.i4.s 10
- IL_0089: sub
- IL_008a: stloc.0
- IL_008b: ldloc.0
- IL_008c: ldc.i4.s 10
- IL_008e: bge.s IL_0039
- IL_0090: br.s IL_00a9
- IL_0092: ldloc.3
- IL_0093: ldind.i4
- IL_0094: ldloc.s V_4
- IL_0096: ldind.i4
- IL_0097: bne.un.s IL_00ad
- IL_0099: ldloc.3
- IL_009a: ldc.i4.4
- IL_009b: conv.i
- IL_009c: add
- IL_009d: stloc.3
- IL_009e: ldloc.s V_4
- IL_00a0: ldc.i4.4
- IL_00a1: conv.i
- IL_00a2: add
- IL_00a3: stloc.s V_4
- IL_00a5: ldloc.0
- IL_00a6: ldc.i4.2
- IL_00a7: sub
- IL_00a8: stloc.0
- IL_00a9: ldloc.0
- IL_00aa: ldc.i4.0
- IL_00ab: bgt.s IL_0092
- IL_00ad: ldloc.0
- IL_00ae: ldc.i4.0
- IL_00af: cgt
- IL_00b1: ldc.i4.0
- IL_00b2: ceq
- IL_00b4: stloc.s V_5
- IL_00b6: leave.s IL_00b8
- IL_00b8: ldloc.s V_5
- IL_00ba: ret
- } // end of method String::EqualsHelper
粗略估算了下,System.String::op_Equality(strin,string)执行时间大概是160。
这样方法1的大概运行时间是165,空间大概是220字节。方法2比只多了一次调用System.String::Empty(程序中的静态变量)
- .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():
- .method public hidebysig specialname instance int32
- get_Length() cil managed internalcall
- {
- } // 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取一个没有实例化数组的长度,那么产生异常就在所难免了。
因此应该这么做:
- string Input = "Yours Input Msg";
- if (Input!=null && Input.Length==0)
- ;
看一下IL:
- .locals init ([0] string Input,[1] bool CS$4$0000)
- IL_0000: nop
- IL_0001: ldstr "Yours Input Msg"
- IL_0006: stloc.0
- IL_0007: ldloc.0
- IL_0008: brfalse.s IL_0018
- IL_000a: ldloc.0
- IL_000b: callvirt instance int32 [mscorlib]System.String::get_Length()
- IL_0010: ldc.i4.0
- IL_0011: ceq
- IL_0013: ldc.i4.0
- IL_0014: ceq
- IL_0016: br.s IL_0019
运行时间大概是在5-13,空间越是30字节。这样,比起Input == ""这个方法,效率大约提高
13-30多倍(因为有短路运算),空间却只要1/7。
因此这些方法中判断字符串是否为空的最佳方法是:
- desStr!=null && desStr.Length==0
本人初涉C#,很多东西掌握的都不是很好,如果你有自己的看法,请告知我,一起学习,一起进步:)