在编译语言中,您所做的只是过早优化。我想解释型语言可能会节省一点,但即使在那里,对于(根据我的经验)编写 for 循环的一种不寻常的方式来说,你的回报也会微乎其微。
要直接针对 C# 回答您的问题,不,编译器不会通过缓存来优化任何内容。在循环期间,我可以很容易地创建一个具有新长度的新数组。因此,它会在每次评估停止条件时加载数组长度。或者更糟的是,我可能没有使用“传统”风格的停止条件,可能需要评估一个函数才能知道停止。
也就是说,这是一个简单的程序:
static void Main( string[] args ) {
int[] integers = new int[] { 1, 2, 3, 4, 5 };
for( int i = 0; i < integers.Length; i++ ) {
Console.WriteLine( i );
}
}
这是IL(删除了nops):
IL_000d: call void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array,
valuetype [mscorlib]System.RuntimeFieldHandle)
IL_0012: stloc.0
IL_0013: ldc.i4.0
IL_0014: stloc.1
IL_0015: br.s IL_0024
IL_0018: ldloc.1
IL_0019: call void [mscorlib]System.Console::WriteLine(int32)
IL_0020: ldloc.1
IL_0021: ldc.i4.1
IL_0022: add
IL_0023: stloc.1
IL_0024: ldloc.1
IL_0025: ldloc.0
IL_0026: ldlen
IL_0027: conv.i4
IL_0028: clt
IL_002a: stloc.2
IL_002b: ldloc.2
IL_002c: brtrue.s IL_0017
您的问题的关键答案是,它是将数组推送到堆栈中的位置 0,然后在 IL_0026 执行调用以获取数组的长度,然后 IL_0028 执行小于比较,最后转到 IL_0017如果评价是真的。
通过缓存数组的长度,您所节省的只是一个 ldlen 和一个 stloc 调用。ldlen 指令应该很快,因为获取数组的长度不会浪费太多时间。
编辑:
与列表的主要区别在于以下指令:
IL_002b: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Count()
callvirt 会占用更多时间,但这个函数实际上所做的只是返回一个私有变量。
你最好不要担心需要几毫秒的事情——比如分块数据库调用,或者优化 SQL 查询以使其更快等等,而不是试图削减单个 IL 操作。