优先用 for 遍历 int[]、double[] 等托管数组,性能高 20%–40%;List<T> 则 foreach 更稳,因底层优化为索引访问;自定义集合若未高效实现 IEnumerable<T>,foreach 会退化为慢接口调用。
用 for 还是 foreach?数组场景下别无脑换
对 int[]、double[] 这类托管数组,for 循环比 foreach 快 20%–40%,因为后者会多一次边界检查和枚举器开销。但换成 list<t> 后,foreach 反而更稳——它底层直接走 count + 索引访问,且编译器能做范围检查优化。
常见错误现象:foreach 遍历自定义集合时性能骤降,其实是没实现 IEnumerable<T> 的高效路径,而是退化成接口调用。
- 纯数组、Span<T>、Memory<T>:优先写
for (int i = 0; i -
List<T>或已知长度的集合:两种都行,但别在循环体里反复调用list.Count(把它提到外面缓存) - 不确定底层结构的
IEnumerable<T>:只能用foreach,强行转ToArray()可能更慢
嵌套循环提前退出:别只靠 break
三层 for 套在一起,想在内层满足条件时立刻跳出全部,光用 break 没用——它只出一层。有人写 goto,但可读性差;也有人加标志位,结果多出分支预测失败。
更直接的做法是把多层逻辑抽成局部函数,配合 return:
bool found = false;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
for (int k = 0; k < p; k++)
{
if (Check(i, j, k))
{
found = true;
goto end;
}
}
}
}
end:不如写成:
bool TryFind(out int i, out int j, out int k)
{
i = j = k = -1;
for (int x = 0; x < n; x++)
for (int y = 0; y < m; y++)
for (int z = 0; z < p; z++)
if (Check(x, y, z))
{
i = x; j = y; k = z;
return true;
}
return false;
}循环不变量:别在 for 里反复算同一个值
比如 for (int i = 0; i ,每次迭代都重新读 <code>list.Count 再乘 2——虽然 JIT 可能优化,但不保险。更危险的是 for (int i = 0; i ,这会让复杂度从 O(n) 变成 O(n²)。
- 把
list.Count、array.Length、常量表达式提出来,声明为readonly局部变量 - 避免在循环条件中调用任何非纯函数(含属性 getter,除非标记了
[MethodImpl(MethodImplOptions.AggressiveInlining)]) - 对
Span<T>或ReadOnlySpan<T>,用Length字段而非Count属性(后者是兼容性包装)
用 Span<T> 替代数组传参:零拷贝的关键
多层循环常出现在矩阵运算、图像处理等场景,如果函数签名是 void Process(int[][] data),每次调用都触发堆分配和 GC 压力;改成 void Process(Span<Span<int>> data),就能复用栈内存或切片已有数组。
容易踩的坑:Span<T> 不能跨 await 边界,也不能作为类字段存储。传入前务必确认源数据生命周期覆盖整个循环过程。
- 初始化用
stackalloc int[1024]或array.AsSpan(),别用new int[n].AsSpan()(堆分配白费) - 嵌套
Span<T>时,外层Span<Span<int>>要求内层数组连续布局,否则得用Memory<T>+.Span动态获取 - 调试时注意:Watch 窗口不支持直接展开
Span<T>,用.ToArray()会破坏零拷贝优势,仅限排查用
实际写多层循环时,最常被忽略的是数据局部性——哪怕算法复杂度没变,把 i 放最外层、j 中间、k 最内层,和反过来,Cache Miss 率可能差 5 倍。这不是玄学,是 CPU 缓存行真实在起作用。











