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
- {
-
- .maxstack 8
- IL_0000: ldarg.0
- IL_0001: ldarg.1
- IL_0002: call bool System.String::Equals(string,
- string)
- IL_0007: ret
- }
发现他还要调用这个函数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 )
-
- .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
- }
然后还要调用System.String::EqualsHelper(string,string):
粗略估算了下,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
- {
- }
这个函数的函数体居然是空的?为什么?那难道这个不起作用吗?我认为是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#,很多东西掌握的都不是很好,如果你有自己的看法,请告知我,一起学习,一起进步:)