这记录在C# specification under 8.8.4 The Foreach Statement(though sadly not in the online readable version):
如果表达式的X类型是数组类型,则存在隐式引用转换
从 X 到 System.Collections.IEnumerable 接口(因为 System.Array 实现
这个界面)。集合类型是 System.Collections.IEnumerable 接口,
枚举器类型是 System.Collections.IEnumerator 接口,和
element type 是数组类型X的元素类型。
我已经强调了记录您所询问的行为的要点。
数组包含整数,因此 foreach 语句将按照上述规则在整数上循环。
数组不是“整数数组的数组”,而是“整数的二维数组”。它是一个具有矩形索引的整数集合,但它仍然是一个整数集合。
请注意,多维数组的元素类型不实现 IEnumerable<T>,但它确实实现了非泛型接口 IEnumerable。
因此,对于集合的定义,数组是整数的集合,即使它是多维的。
请注意,数组在 C# 编译器中有一个特殊的位置,它会在很多情况下专门处理这些,所以除了它是一个数组之外,你可能无法从反射中推断出任何东西。
要查看编译器为数组提供的一些特殊处理,请尝试在 LINQPad 中执行此代码,然后单击 IL 选项卡:
void Main()
{
}
public void A()
{
int[] a = new int[10];
foreach (var x in a) { }
}
public void B()
{
int[] a = new int[10];
for (int i = 0; i < a.Length; i++) { }
}
你会得到这个:
IL_0000: ret
A:
IL_0000: ldc.i4.s 0A
IL_0002: newarr System.Int32
IL_0007: stloc.0 // a
IL_0008: ldloc.0 // a
IL_0009: stloc.1 // CS$6$0000
IL_000A: ldc.i4.0
IL_000B: stloc.2 // CS$7$0001
IL_000C: br.s IL_0016
IL_000E: ldloc.1 // CS$6$0000
IL_000F: ldloc.2 // CS$7$0001
IL_0010: ldelem.i4
IL_0011: pop
IL_0012: ldloc.2 // CS$7$0001
IL_0013: ldc.i4.1
IL_0014: add
IL_0015: stloc.2 // CS$7$0001
IL_0016: ldloc.2 // CS$7$0001
IL_0017: ldloc.1 // CS$6$0000
IL_0018: ldlen
IL_0019: conv.i4
IL_001A: blt.s IL_000E
IL_001C: ret
B:
IL_0000: ldc.i4.s 0A
IL_0002: newarr System.Int32
IL_0007: stloc.0 // a
IL_0008: ldc.i4.0
IL_0009: stloc.1 // i
IL_000A: br.s IL_0010
IL_000C: ldloc.1 // i
IL_000D: ldc.i4.1
IL_000E: add
IL_000F: stloc.1 // i
IL_0010: ldloc.1 // i
IL_0011: ldloc.0 // a
IL_0012: ldlen
IL_0013: conv.i4
IL_0014: blt.s IL_000C
IL_0016: ret
请注意,在任何地方都没有调用GetEnumerator,这意味着foreach 没有使用GetEnumerator。相反,它被重写为使用索引的 for 循环,因为在 .NET 中实现数组的方式实际上更快。