C#循环:遍历数组

16

如果我有以下循环:

foreach (string pass in new string[] { "pass1", "pass2", "pass3" })
{
 x = pass; //etc
}

这个匿名字符串数组是最初创建一次,还是每次通过都重新创建一次?

我相信是前者,但同事们坚信这是一个等待发生的错误,因为他们说foreach循环的每次迭代都会导致一个新的字符串数组被创建。

VS反汇编代码表明我是正确的,但我想要确定。

我们研究这个的原因是为了尝试理解一个神秘的错误,报告说在迭代过程中集合已经被改变。


10
你可以轻松测试它,例如将DateTime值放入字符串中并检查每次迭代中的成员。 - Ofer Zelig
另一种向他们展示它只被评估一次的方法是在调试模式下逐步执行代码。创建字符串数组的代码只会被突出显示一次。 - Frédéric Hamidi
5
“一个神秘的错误报告,指出在迭代集合时该集合已被更改”,为什么不把这个作为问题发布呢? - D Stanley
3
“一个神秘的错误报告表明在迭代集合时该集合已被更改。”- 这暗示着您的代码正在循环中从集合中移除项目。这就是您应该寻找的内容。 - Robert Levy
我认为这与 foreach (var rec in Database.ListMillionsOfRecords()) 相同,只会对数据库进行一次访问。 - Marc
显示剩余5条评论
5个回答

27
根据Eric Lippert博客规范, foreach循环是以下语法糖的简写形式:
{
  IEnumerator<string> e = ((IEnumerable<string>)new string[] { "pass1", "pass2", "pass3" }).GetEnumerator();
   try
   { 
     string pass; // OUTSIDE THE ACTUAL LOOP
      while(e.MoveNext())
      {
        pass = (string)e.Current;
        x = pass;
      }
   }
   finally
   { 
      if (e != null) ((IDisposable)e).Dispose();
   }
}

正如您所看到的,枚举器是在循环之前创建的。

@Rawling 正确指出,数组在编译器中处理方式略有不同。foreach循环被优化成使用数组的for循环。根据C# foreach的内部机制,您的C# 5代码将如下所示:

string[] tempArray;
string[] array = new string[] { "pass1", "pass2", "pass3" };
tempArray = array;

for (string counter = 0; counter < tempArray.Length; counter++)
{
    string pass = tempArray[counter];
    x = pass;
}

初始化也仅会发生一次。


4
从C# 5.0开始,变量 pass 将位于循环内部。 - Botz3000
4
我认为这对于数组来说略有不同...它不会将foreach循环转换为for循环吗? - Rawling
1
@lazy 说实话,你的第一个代码块是最好的答案,因为它符合规范(8.8.4)- 数组的 for 循环只是一点优化。 - Rawling
1
我尽力而为 :) (并不总是成功...) - Rawling
1
谢谢 - 问题已回答。 - haughtonomous
显示剩余7条评论

5

如果你在ILSpy中查看,这段代码会被翻译成类似以下的内容

string[] array = new string[]
{
    "pass1",
    "pass2",
    "pass3"
};
for (int i = 0; i < array.Length; i++)
{
    string pass = array[i];
}

是的,该数组仅创建一次。

然而,最好的参考资料可能是C#规范的8.8.4节,它基本上会告诉您LazyBerezovsky的答案所做的事情。


+1并感谢指出关于数组不同foreach编译的问题! - Sergey Berezovskiy

2

它最初只创建一次。

我尝试了Ofer Zelig(评论中提到的)的建议。

foreach (DateTime pass in new DateTime[] { DateTime.Now, DateTime.Now, DateTime.Now })
{
    int x = pass.Second; //etc
}

设置一个断点。即使在迭代之间等待,它也会为所有三个迭代提供相同的秒数。


确保的好方法 :) 我建议使用 new[] { DateTime.Now, DateTime.Now, DateTime.Now } 来使代码看起来更短 :) - nawfal

1

您可以进行测试(有很多方法可以进行测试,但这是其中一种选择):

string pass4 = "pass4";
foreach (string pass in new string[] { "pass1", "pass2", "pass3", pass4 })
{
    pass4="pass5 - oops";
    x = pass; //etc
}

然后看看会发生什么。

你会发现你是正确的 - 它只执行了一次。


1
谢谢 - 问题已回答。 - haughtonomous

1
下面的示例应该回答了数组是否被重新创建的问题。
        int i = 0;
        int last = 0;

        foreach (int pass in new int[] { i++, i++, i++, i++, i++, i++, i++ })
        {
            if (pass != last)
            {
                throw new Exception("Array is reintialized!");
            }
            last++;
        }

        if (i > 7)
        {
            throw new Exception("Array is reintialized!");
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接