初读 c# IL中间语言
对一段c#编写的代码,有一些疑问,想通过IL中间语言看看,编译后是怎么处理的。代码如下:
1 static StringBuilder sb = new StringBuilder(); 2 3 static int filecount = 0; 4 static int dirCount = 0; 5 6 /// <summary> 7 /// 获取目录path下所有子文件名 8 /// </summary> 9 public static List<string> getAllFiles(String path, int depth = 0) 10 { 11 List<string> strs = new List<string>(); 12 if (System.IO.Directory.Exists(path)) 13 { 14 //所有子文件名 15 string[] files = System.IO.Directory.GetFiles(path); 16 foreach (string file in files) 17 { 18 sb.Append(new string('*', depth * 3) + file + "\r\n"); 19 strs.Add(file); 20 filecount++; 21 } 22 //所有子目录名 23 string[] Dirs = System.IO.Directory.GetDirectories(path); 24 foreach (string dir in Dirs) 25 { 26 dirCount++; 27 sb.Append(new string('*', depth * 3) + dir + "\r\n"); 28 var tmp = getAllFiles(dir, depth + 1); //子目录下所有子文件名 29 if (!tmp.Equals("")) 30 { 31 strs.AddRange(tmp); 32 } 33 } 34 } 35 return strs; 36 }
这段代码的功能是很简单的:给定一个文件夹,返回下面的所有文件(递归遍历)。我的疑问:在第11行,递归调用的时候,strs变量(用来存放所有文件的名称列表)能够保存到所有文件名吗?程序运行的结果告诉我,代码没有任何问题。代码是别人写的,如果是我写的话,可能会在方法之外定义一个变量,在递归的时候,遇到文件的时候,把名字里存进去,而递归方法就不用返回值。当然按照依赖关系来看的话,方法内部用的变量,要么是内部自定义的,要么是参数传进来的,这样对外没有依赖,方法的复用性高。毋庸置疑,代码没啥问题。第一行的sb是我加进去的。通过查看IL代码,明白了一些道理,看到了编译器为我们程序处理的一些东西,比较感兴趣,就把这段IL摘下来:
.method public hidebysig static class [mscorlib]System.Collections.Generic.List`1<string> getAllFiles(string path, [opt] int32 depth) cil managed { .param [2] = int32(0x00000000) // 代码大小 262 (0x106) .maxstack 4 .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<string> strs, [1] string[] files, [2] string file, [3] string[] Dirs, [4] string dir, [5] class [mscorlib]System.Collections.Generic.List`1<string> tmp, [6] class [mscorlib]System.Collections.Generic.List`1<string> CS$1$0000, [7] bool CS$4$0001, [8] string[] CS$6$0002, [9] int32 CS$7$0003) // 带$符号的变量是编译器定义的临时变量 IL_0000: nop IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<string>::.ctor() IL_0006: stloc.0 //把上面实例化好的对象保存到第一个变量中,也就是strs IL_0007: ldarg.0 //加载第一个参数,也就是path IL_0008: call bool [mscorlib]System.IO.Directory::Exists(string) IL_000d: ldc.i4.0 //在堆栈中加载一个int类型,值为0的常量 IL_000e: ceq IL_0010: stloc.s CS$4$0001 //把 call的结果和0作比较,把比较的结果存到这个变量中 IL_0012: ldloc.s CS$4$0001 //加载这个变量到堆栈中 IL_0014: brtrue IL_00fe //如果call的结果和0相等就跳转了,也就是说001中存了true IL_0019: nop IL_001a: ldarg.0 IL_001b: call string[] [mscorlib]System.IO.Directory::GetFiles(string) IL_0020: stloc.1 //存储到第一个变量:files IL_0021: nop IL_0022: ldloc.1 //加载到第一个变量到堆栈里 IL_0023: stloc.s CS$6$0002 //把这个变量的值存到002变量中 IL_0025: ldc.i4.0 IL_0026: stloc.s CS$7$0003 //把0存储到003的变量中 IL_0028: br.s IL_006c //跳转到006c行 IL_002a: ldloc.s CS$6$0002 //加载数组 IL_002c: ldloc.s CS$7$0003 //加载003,此时值为0 IL_002e: ldelem.ref //取数组中的一个元素 IL_002f: stloc.2 //存到变量2 file中 IL_0030: nop IL_0031: ldsfld class [mscorlib]System.Text.StringBuilder Client.Program::sb //加载sb字段 IL_0036: ldc.i4.s 42 //加载常量42 IL_0038: ldarg.1 //加载第二个参数depth IL_0039: ldc.i4.3 //加载常量3 IL_003a: mul //3*depth 乘法 IL_003b: newobj instance void [mscorlib]System.String::.ctor(char, int32) IL_0040: ldloc.2 IL_0041: ldstr "\r\n" IL_0046: call string [mscorlib]System.String::Concat(string, string, string) //实例化了string的实例,并把前面3个字符串给拼接起来 IL_004b: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string) IL_0050: pop IL_0051: ldloc.0 //加载第一个变量strs IL_0052: ldloc.2 //加载第三个变量file IL_0053: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::Add(!0) IL_0058: nop IL_0059: ldsfld int32 Client.Program::filecount IL_005e: ldc.i4.1 IL_005f: add //filecount加1 IL_0060: stsfld int32 Client.Program::filecount IL_0065: nop IL_0066: ldloc.s CS$7$0003 加载了003,值为0 IL_0068: ldc.i4.1 IL_0069: add IL_006a: stloc.s CS$7$0003 给003加了1,此时003的值为1,这么看来003就是个计数器,用来控制循环 IL_006c: ldloc.s CS$7$0003 //加载003,第一次,变量中为0 IL_006e: ldloc.s CS$6$0002 //加载002,此时为files数组 IL_0070: ldlen //取数组长度,并转为int类型 IL_0071: conv.i4 IL_0072: clt IL_0074: stloc.s CS$4$0001 //看003是否比数组长度小,把比较结果存到001中 IL_0076: ldloc.s CS$4$0001 IL_0078: brtrue.s IL_002a //如果为true,跳转到002a行 IL_007a: ldarg.0 IL_007b: call string[] [mscorlib]System.IO.Directory::GetDirectories(string) IL_0080: stloc.3 IL_0081: nop IL_0082: ldloc.3 IL_0083: stloc.s CS$6$0002 IL_0085: ldc.i4.0 IL_0086: stloc.s CS$7$0003 IL_0088: br.s IL_00ef IL_008a: ldloc.s CS$6$0002 IL_008c: ldloc.s CS$7$0003 IL_008e: ldelem.ref IL_008f: stloc.s dir IL_0091: nop IL_0092: ldsfld int32 Client.Program::dirCount IL_0097: ldc.i4.1 IL_0098: add IL_0099: stsfld int32 Client.Program::dirCount IL_009e: ldsfld class [mscorlib]System.Text.StringBuilder Client.Program::sb IL_00a3: ldc.i4.s 42 IL_00a5: ldarg.1 IL_00a6: ldc.i4.3 IL_00a7: mul IL_00a8: newobj instance void [mscorlib]System.String::.ctor(char, int32) IL_00ad: ldloc.s dir IL_00af: ldstr "\r\n" IL_00b4: call string [mscorlib]System.String::Concat(string, string, string) IL_00b9: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string) IL_00be: pop IL_00bf: ldloc.s dir IL_00c1: ldarg.1 IL_00c2: ldc.i4.1 IL_00c3: add IL_00c4: call class [mscorlib]System.Collections.Generic.List`1<string> Client.Program::getAllFiles(string, int32) IL_00c9: stloc.s tmp IL_00cb: ldloc.s tmp IL_00cd: ldstr "" IL_00d2: callvirt instance bool [mscorlib]System.Object::Equals(object) IL_00d7: stloc.s CS$4$0001 IL_00d9: ldloc.s CS$4$0001 IL_00db: brtrue.s IL_00e8 IL_00dd: nop IL_00de: ldloc.0 IL_00df: ldloc.s tmp IL_00e1: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::AddRange(class [mscorlib]System.Collections.Generic.IEnumerable`1<!0>) IL_00e6: nop IL_00e7: nop IL_00e8: nop IL_00e9: ldloc.s CS$7$0003 IL_00eb: ldc.i4.1 IL_00ec: add IL_00ed: stloc.s CS$7$0003 IL_00ef: ldloc.s CS$7$0003 IL_00f1: ldloc.s CS$6$0002 IL_00f3: ldlen IL_00f4: conv.i4 IL_00f5: clt IL_00f7: stloc.s CS$4$0001 IL_00f9: ldloc.s CS$4$0001 IL_00fb: brtrue.s IL_008a IL_00fd: nop IL_00fe: ldloc.0 IL_00ff: stloc.s CS$1$0000 IL_0101: br.s IL_0103 IL_0103: ldloc.s CS$1$0000 IL_0105: ret } // end of method Program::getAllFiles
我加注释的地方,就是方法从头开始到第一个循环结束的地方,后面的循环和这个类似,就不解读了。