这段代码居然运行正常
乍一看还以为这个世界变得太快自己不认识了,再一琢磨,原来是凑巧而已,世界虽然变了点,但基本的东西还是没变的。
ArrayList test = new ArrayList();
int i;
for(i = 0;i < 20;i++)
test.Add(i);
for (i = 0;i < test.Count;i++)
if (i % 2 != 0)
test.RemoveAt(i);
for(i = 0;i < test.Count;i++)
Console.WriteLine("Array [{0}] is {1}",i,test[i]);
Console.Read();
这种类型的代码在Delphi下别说是运行结果正常,根本就是连运行都运行不了的,最后肯定会报出来一个“List index out of bounds()”这样的错误。
首先是它运行不报错,说明For循环与在delphi下是不太一样的。
Delphi代码:
for i := 0 to test.Count - 1 do
if (i mod 2) <> 0 then
test.Delete(i);
它的for循环是从一开始的时候就把初始与终止条件定好了,在循环过程中其终止条件的值是不变的(不知道如何把Delphi的CPU视图里的ASM代码拷出来,截个图算了),
看最底部的两句dec esi、jnz -$20。在最开始进入循环的时候这个esi就定了。所以说在循环体内虽然有test.Delete(i)这样的语句,但其由test.Count – 1计算得的终止条件没有变,它在最开始的时候就已经把它取出来(当然它不是取终止条件,实际上是直接把循环次数算出来给了esi的,把循环初始条件改为非0开始能看得更明白点)。所以上面这种从头开始删的方法是会报错的。
而在C#里,上面那段代码查看IL有
IL_0036: callvirt instance int32 [mscorlib]System.Collections.ArrayList::get_Count()
IL_003b: blt.s IL_0024
类似的语句。这里的for循环在执行过程中其终止条件是一直在变化的(这里for的流程决定了其for循环的强大之处;Delphi里的那个for本身的语法没有C/C#里的这么复杂,所以它的编译器作了点优化。PS:Delphi的虽然比较的简洁一点,但有时也太不方便,比如无法控制步长)。你删尽管删好了,反正它的i只到当时test.Count那个地方就停了。所以上面的代码可以运行。
至于运行正常那正好是凑巧而已了。每次RemoveAt掉一个使ArrayList后部的元素都上提的同时而i又增1,由此每次RemoveAt后for循环遍历漏掉一个元素的情况下,正好那个漏掉的元素本身是不符合条件的,反正漏掉它也无关痛痒。所以上面的运行结果看起来很正常。其实把
if (i % 2 != 0)
改为
if (i % 3 != 0)
就可以看出结果不正常了,当然if (i % 3 == 0) 这样的条件出来结果也是正常的,也是因为漏掉的元素无关痛痒的缘故。
当然即使能运行正常也不要写上面那样的代码好,只有20个元素看不太出来,如果元素一多就严重影响性能了啦。
还好世界其实并没有变。只是一个for不同而已,删东西还是从尾部开始删。
for(i = test.Count - 1;i >=0;i--)
if (i % 3 != 0)
test.RemoveAt(i);
一切恢复正常,速度也飞快。