给出以下C#代码:
int[,] array2D = new int[10, 10];
int sum = 0;
foreach (var i in array2D)
{
sum += i;
}
问题是:是什么导致i
的类型被正确推断为int
?
这并不是一件容易的事,因为array2D是一个矩形数组。它没有实现IEnumerable<int>
。它还实现了GetEnumerator()
方法,该方法返回System.Collections.IEnumerator
。因此,我希望i
的类型为object
。
我的代码正在使用.net 4.03。
相关问题:Why do C# Multidimensional arrays not implement IEnumerable?。
答案 0 :(得分:5)
数组是使用技巧和黑客实现的东西。实际上,Array没有实现IEnumerable<T>
CLR使用SZArrayHelper欺骗我们。
Related question of mine和HansPassant有一个related answer here。
对于foreach
代码,IL也是不同的,c#编译器本身为2Darrays和单维数组生成不同的IL。它甚至没有遗憾地调用GetEnumerator
方法:(。
毛茸茸的实施细节
我没有实现这一点,但这是一组非常有趣的工作 在加载器中,还有一些看起来有点hacky的C#代码。你可能会 想知道有人如何仅在某些数组类型上定义泛型方法, 生成数组时没有相应源的类型 码。使用IList in V1,我们将System.Array作为基类 Int32 []和Object []子类,但我们无法添加方法 数组本身,因为方法实现没有意义 除了SZArray以外的任何东西。作为内部实施 没有人需要知道的细节,我们目前有一个SZArrayHelper 定义所有有趣的方法体的类,我们传递一个 数组作为“this”指针的方法。这导致一些非常 奇怪的内部代码,像这样:
sealed class SZArrayHelper
{
internal int get_Count<T>()
{
//! Warning: "this" is an array, not an SZArrayHelper. See comments above
//! or you may introduce a security hole!
T[] _this = this as T[];
BCLDebug.Assert(_this!= null, "this should be a T[]");
return _this.Length;
}
}
幸运的是,CLR团队已经做了所有复杂的恶事 类型系统以你想要的方式工作,这是完全的 您不需要了解的内部细节。有时建立 一个有趣的类型系统,有效的方式,你需要发挥 一些会让你的计算机科学教授追捕你的游戏 下来。
我们努力的最终结果是您可以调用通用算法 消耗IList并传递一个通用集合(如 List)或数组(如String [])并获得高效元素 访问没有额外的逻辑强迫你的脸。你没有 制作任何愚蠢的适配器类或类似的东西。
以上段落来自Related article,其中涉及SZArray
。
简直就是神秘。我想分享我目前所知的内容,我知道这不能提供答案,但我希望这会有所帮助。
答案 1 :(得分:3)
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语句将按照上述规则循环onts。
数组不是“整数数组”,它是“2d-int of int”。它是一组具有矩形形状索引的整数,但它仍然是一组整数。
请注意,多维数组不会为其元素类型实现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中实现的方式实际上更快。
答案 2 :(得分:2)
它不是很直观,因为它是一个多维数组,但它是枚举这种数组的预期行为,it's documented:
使用多维数组,您可以使用相同的方法进行迭代 通过元素,例如:
int[,] numbers2D = new int[3, 2] { { 9, 99 }, { 3, 33 }, { 5, 55 } };
// Or use the short form:
// int[,] numbers2D = { { 9, 99 }, { 3, 33 }, { 5, 55 } };
foreach (int i in numbers2D)
{
System.Console.Write("{0} ", i);
}
// Output: 9 99 3 33 5 55