for(int tempCount=0;tempCount<list.count;tempcount++)
{
if(list[tempCount].value==value)
{
// Some code.
}
}
foreach(object row in list)
{
if(row.value==value)
{
//Some coding
}
}
for(int tempCount=0;tempCount<list.count;tempcount++)
{
if(list[tempCount].value==value)
{
// Some code.
}
}
foreach(object row in list)
{
if(row.value==value)
{
//Some coding
}
}
这部分取决于list
的确切类型,也取决于您使用的CLR。
无论循环中是否进行了任何实际工作,它是否有任何显著意义将取决于情况。在几乎所有情况下,性能差异并不显著,但可读性方面foreach
循环更为有利。
我个人会使用LINQ来避免使用“if”:
foreach (var item in list.Where(condition))
{
}
编辑:对那些声称使用 foreach
迭代 List<T>
会产生与 for
循环相同代码的人,这里有证据表明它们并不相同:
static void IterateOverList(List<object> list)
{
foreach (object o in list)
{
Console.WriteLine(o);
}
}
生成以下语言的中间代码:
.method private hidebysig static void IterateOverList(class [mscorlib]System.Collections.Generic.List`1<object> list) cil managed
{
// Code size 49 (0x31)
.maxstack 1
.locals init (object V_0,
valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object> V_1)
IL_0000: ldarg.0
IL_0001: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<object>::GetEnumerator()
IL_0006: stloc.1
.try
{
IL_0007: br.s IL_0017
IL_0009: ldloca.s V_1
IL_000b: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::get_Current()
IL_0010: stloc.0
IL_0011: ldloc.0
IL_0012: call void [mscorlib]System.Console::WriteLine(object)
IL_0017: ldloca.s V_1
IL_0019: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::MoveNext()
IL_001e: brtrue.s IL_0009
IL_0020: leave.s IL_0030
} // end .try
finally
{
IL_0022: ldloca.s V_1
IL_0024: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>
IL_002a: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_002f: endfinally
} // end handler
IL_0030: ret
} // end of method Test::IterateOverList
编译器对数组的处理方式不同,将foreach
循环基本上转换为for
循环,但不包括List<T>
。以下是数组的等效代码:
static void IterateOverArray(object[] array)
{
foreach (object o in array)
{
Console.WriteLine(o);
}
}
// Compiles into...
.method private hidebysig static void IterateOverArray(object[] 'array') cil managed
{
// Code size 27 (0x1b)
.maxstack 2
.locals init (object V_0,
object[] V_1,
int32 V_2)
IL_0000: ldarg.0
IL_0001: stloc.1
IL_0002: ldc.i4.0
IL_0003: stloc.2
IL_0004: br.s IL_0014
IL_0006: ldloc.1
IL_0007: ldloc.2
IL_0008: ldelem.ref
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: call void [mscorlib]System.Console::WriteLine(object)
IL_0010: ldloc.2
IL_0011: ldc.i4.1
IL_0012: add
IL_0013: stloc.2
IL_0014: ldloc.2
IL_0015: ldloc.1
IL_0016: ldlen
IL_0017: conv.i4
IL_0018: blt.s IL_0006
IL_001a: ret
} // end of method Test::IterateOverArray
有趣的是,我在 C# 3 规范中找不到这个记录...
List<T>
。 - Jon Skeetforeach
循环与使用for
相当。始终首先考虑可读性,只有在有证据表明它能带来可衡量的性能优势时才进行微观优化。 - Jon Skeetfor
循环被编译成大致等价于以下代码:
int tempCount = 0;
while (tempCount < list.Count)
{
if (list[tempCount].value == value)
{
// Do something
}
tempCount++;
}
相比之下,foreach
循环被编译成大致等同于以下代码的代码:
using (IEnumerator<T> e = list.GetEnumerator())
{
while (e.MoveNext())
{
T o = (MyClass)e.Current;
if (row.value == value)
{
// Do something
}
}
}
所以你可以看到,这将完全取决于枚举器的实现方式与列表索引器的实现方式。事实证明,基于数组的类型的枚举器通常是这样编写的:
private static IEnumerable<T> MyEnum(List<T> list)
{
for (int i = 0; i < list.Count; i++)
{
yield return list[i];
}
}
所以你可以看到,在这种情况下,它不会有太大的影响,但是链表的枚举器可能看起来像这样:
private static IEnumerable<T> MyEnum(LinkedList<T> list)
{
LinkedListNode<T> current = list.First;
do
{
yield return current.Value;
current = current.Next;
}
while (current != null);
}
public T this[int index]
{
LinkedListNode<T> current = this.First;
for (int i = 1; i <= index; i++)
{
current = current.Next;
}
return current.value;
}
正如您所看到的,循环中多次调用此函数将比使用可以记住列表中位置的枚举器慢得多。
一个简单的半验证测试。我进行了一个小测试,只是为了看看。以下是代码:
static void Main(string[] args)
{
List<int> intList = new List<int>();
for (int i = 0; i < 10000000; i++)
{
intList.Add(i);
}
DateTime timeStarted = DateTime.Now;
for (int i = 0; i < intList.Count; i++)
{
int foo = intList[i] * 2;
if (foo % 2 == 0)
{
}
}
TimeSpan finished = DateTime.Now - timeStarted;
Console.WriteLine(finished.TotalMilliseconds.ToString());
Console.Read();
}
以下是 foreach 部分:
foreach (int i in intList)
{
int foo = i * 2;
if (foo % 2 == 0)
{
}
}
int foo = intList[i];
static void Main(string[] args)
{
List<int> intList = new List<int>();
Console.WriteLine("Generating data.");
for (int i = 0; i < 134217728 ; i++)
{
intList.Add(i);
}
Console.Write("Calculating for loop:\t\t");
Stopwatch time = new Stopwatch();
time.Start();
for (int i = 0; i < intList.Count; i++)
{
int foo = intList[i] * 2;
if (foo % 2 == 0)
{
}
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms");
Console.Write("Calculating foreach loop:\t");
time.Reset();
time.Start();
foreach (int i in intList)
{
int foo = i * 2;
if (foo % 2 == 0)
{
}
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms");
Console.Read();
}
以下是结果:
正在生成数据。 计算for循环:2458毫秒 计算foreach循环:2005毫秒
将它们交换以查看它是否处理事物的顺序会产生几乎相同的结果。
注意:这个答案更适用于Java而非C#,因为C#在LinkedLists上没有索引器,但我认为总体观点仍然成立。
如果你正在使用的list
是一个LinkedList
,那么对于大型列表,使用下标代码(类似数组的访问)的性能比使用foreach
中的IEnumerator
要差得多。
当您使用下标语法:list[10000]
访问LinkedList
中的第10000个元素时,链表将从头结点开始,并遍历Next
指针10000次,直到到达正确的对象。显然,如果您在循环中这样做,你会得到:
list[0]; // head
list[1]; // head.Next
list[2]; // head.Next.Next
// etc.
GetEnumerator
(隐式使用forach
语法)时,您将获得一个指向头节点的IEnumerator
对象。每次调用MoveNext
时,该指针将移动到下一个节点,如下所示:IEnumerator em = list.GetEnumerator(); // Current points at head
em.MoveNext(); // Update Current to .Next
em.MoveNext(); // Update Current to .Next
em.MoveNext(); // Update Current to .Next
// etc.
LinkedList
的情况下,数组索引器方法会变得越来越慢,循环次数越多(它必须一遍又一遍地通过相同的头指针)。而IEnumerable
只在常数时间内运行。list
的类型,如果list
不是LinkedList
而是一个数组,则行为完全不同。LinkedList<T>
文档,它有一个相当不错的API。最重要的是,它没有像Java一样的get(int index)
方法。尽管如此,我想这个观点仍然适用于任何其他类似列表的数据结构,只要它们公开了比特定的IEnumerator
更慢的索引器。 - Tom Lokhorst正如其他人提到的一样,性能实际上并不重要,因为循环中使用了 IEnumerable
/IEnumerator
,所以 foreach 总是会慢一点。编译器将该结构转换为对该接口的调用,并且在 foreach 结构的每一步中都会调用一个函数和一个属性。
IEnumerator iterator = ((IEnumerable)list).GetEnumerator();
while (iterator.MoveNext()) {
var item = iterator.Current;
// do stuff
}
这是C#中结构的等效扩展。您可以想象基于MoveNext和Current的实现如何影响性能。而在数组访问中,您没有这些依赖关系。
List<T>
,那么调用索引器仍然会导致(可能是内联的)开销。这不像是裸的数组访问。 - Jon Skeetforeach
循环而不是for
循环肯定更好。foreach
结构比简单的for-loop
(每步2个周期)更快(每步1.5个周期),除非循环已展开(每步1.0个周期)。for
、while
或do-while
结构的原因。╔══════════════════════╦═══════════╦═══════╦════════════════════════╦═════════════════════╗
║ Method ║ List<int> ║ int[] ║ Ilist<int> onList<Int> ║ Ilist<int> on int[] ║
╠══════════════════════╬═══════════╬═══════╬════════════════════════╬═════════════════════╣
║ Time (ms) ║ 23,80 ║ 17,56 ║ 92,33 ║ 86,90 ║
║ Transfer rate (GB/s) ║ 2,82 ║ 3,82 ║ 0,73 ║ 0,77 ║
║ % Max ║ 25,2% ║ 34,1% ║ 6,5% ║ 6,9% ║
║ Cycles / read ║ 3,97 ║ 2,93 ║ 15,41 ║ 14,50 ║
║ Reads / iteration ║ 16 ║ 16 ║ 16 ║ 16 ║
║ Cycles / iteration ║ 63,5 ║ 46,9 ║ 246,5 ║ 232,0 ║
╚══════════════════════╩═══════════╩═══════╩════════════════════════╩═════════════════════╝
在测试两个循环的速度时,有一个更有趣的事实很容易被忽略:使用调试模式不会让编译器使用默认设置来优化代码。
这导致了一个有趣的结果,即在调试模式下,foreach 比 for 循环更快。而在发布模式下,for 循环比 foreach 更快。显然,编译器有更好的方法来优化 for 循环,而 foreach 循环则涉及多个方法调用,因此更难优化。顺便说一句,for 循环是如此基础,以至于甚至可能被 CPU 自身优化。
using System;
using System.Diagnostics;
class MainProgram
{
static void Main()
{
int[] randomNumbers = new int[9999999];
var randomGenerate = new Random();
// Random values are initialized to array
for (int i = 0; i < randomNumbers.Length; i++)
{
randomNumbers[i] = randomGenerate.Next(100);
}
Stopwatch timer = new Stopwatch();
// Calculate the time taken by the for loop
timer.Start();
for (int i = 0; i < randomNumbers.Length; i++)
{
int x = randomNumbers[i];
}
timer.Stop();
Console.WriteLine("Time taken by For loop: " + timer.ElapsedMilliseconds + "ms");
// Calculate the time taken by the foreach loop
timer.Reset();
timer.Start();
foreach (int x in randomNumbers)
{
int y = x;
}
timer.Stop();
Console.WriteLine("Time taken by Foreach loop: " + timer.ElapsedMilliseconds + "ms");
}
}
这个程序的输出因系统性能而异,但每次执行时,for循环都比foreach循环快。
Output:
Time taken by For loop: 20ms
Time taken by Foreach loop: 25ms
list
的类型确实有一个count
成员而不是Count
。 - Jon Skeet