为什么foreach(array2D中的var i)在多维数组上工作?

时间:2014-03-07 14:20:01

标签: c# .net multidimensional-array foreach

给出以下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?

3 个答案:

答案 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