在.net中,使用"foreach"迭代IEnumerable会创建一个副本吗
本文关键字:quot 创建 副本 一个 net 使用 foreach IEnumerable 迭代 ValueType | 更新日期: 2023-09-27 17:50:16
在。net中,使用"foreach"来迭代IEnumerable的实例将创建一个副本?那么我应该更喜欢用"for"而不是"foreach"吗?
我写了一些代码来证明:
struct ValueTypeWithOneField
{
private Int64 field1;
}
struct ValueTypeWithFiveField
{
private Int64 field1;
private Int64 field2;
private Int64 field3;
private Int64 field4;
private Int64 field5;
}
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("one field");
Test<ValueTypeWithOneField>();
Console.WriteLine("-----------");
Console.WriteLine("Five field");
Test<ValueTypeWithFiveField>();
Console.ReadLine();
}
static void Test<T>()
{
var test = new List<T>();
for (int i = 0; i < 5000000; i++)
{
test.Add(default(T));
}
Stopwatch sw = new Stopwatch();
for (int i = 0; i < 5; i++)
{
sw.Start();
foreach (var item in test)
{
}
sw.Stop();
Console.WriteLine("foreach " + sw.ElapsedMilliseconds);
sw.Restart();
for (int j = 0; j < test.Count; j++)
{
T temp = test[j];
}
sw.Stop();
Console.WriteLine("for " + sw.ElapsedMilliseconds);
sw.Reset();
}
}}
这是我运行代码后得到的结果:
one field
foreach 68
for 72
foreach 68
for 72
foreach 67
for 72
foreach 64
for 73
foreach 68
for 72
-----------
Five field
foreach 272
for 193
foreach 273
for 191
foreach 272
for 190
foreach 271
for 190
foreach 275
for 188
从结果中可以看出,"foreach"总是比"for"花费更多的时间。
所以我应该更喜欢使用"for"而不是"foreach"时,通过值类型的泛型集合迭代?
注:谢谢提醒,代码和结果是我编辑的。但是,每个人都比每个人跑得慢。
你的问题太复杂了。分解。
号使用"foreach"迭代值类型序列创建序列的副本吗?
使用"foreach"来迭代值类型序列是否创建每个值的副本?
是的。
使用"for"对值类型的索引序列进行等效迭代是否创建每个值的副本?
通常,是的。如果您知道关于集合的特殊情况,例如它是一个数组,那么您可以做一些事情来避免复制。但是在索引集合的一般情况下,索引序列返回序列中值的副本,而不是对包含值的存储位置的引用。 对值类型执行操作是否会复制该值? 差不多。值类型由值
使用"foreach"或"for"来迭代引用类型的序列是否复制引用?
是的。引用类型表达式的值是引用。该引用在使用时被复制。
那么,就拷贝行为而言,值类型和引用类型有什么区别呢?
值类型按值复制。引用类型复制引用,但不复制被引用的对象。一个16字节的值类型每次使用它都会复制16个字节。16字节的引用类型在每次使用它时都会复制4(或8)字节的引用。
foreach循环比for循环慢吗?
通常是。foreach循环通常要做更多的工作,因为它要创建一个枚举数并调用枚举数上的方法,而不仅仅是增加一个整数。整数增量非常快。另外,不要忘记foreach循环中的枚举数必须被处理掉,这也需要花费时间。
我是否应该使用for循环而不是foreach循环,因为for循环有时会快几微秒?
。这是愚蠢的。您应该根据以客户为中心的经验数据做出明智的工程决策。foreach循环的额外负担很小。客户可能永远不会注意到。你应该做的是:
- 根据客户输入设定绩效目标
- 衡量你是否达到了你的目标
- 如果你没有,找到最慢的东西使用分析器
- 修复它
- 重复,直到达到你的目标
您的测试不准确;在foreach
版本中,您实际上是旋转枚举器并从列表中检索每个值(即使您不使用它)。在for
版本中,除了查看其Count
属性外,您根本不用对列表做任何操作。您实际上是在测试枚举数遍历集合的性能,并将其与对整数变量递增相同次数的性能进行比较。
要创建奇偶校验,您需要声明一个临时变量,并在for
循环的每次迭代中分配它。
话虽如此,你的问题的答案是是。每次赋值或return
语句都会创建该值的副本。
性能
下面的伪代码可以解释为什么在这个特定的实例中,foreach
比使用for
要慢一些:
foreach
:
try
{
var en = test.GetEnumerator(); //creates a ListEnumerator
T item;
while(en.MoveNext()) // MoveNext increments the current index and returns
// true if the new index is valid, or false if it's
// beyond the end of the list. If it returns true,
// it retrieves the value at that index and holds it
// in an instance variable
{
item = en.Current; // Current retrieves the value of the current instance
// variable
}
}
finally { }
for
:
int index = -1;
T item;
while(++index < test.Count)
{
item = test[index];
}
可以看到,for
实现中的代码更少,而且foreach
在for
之上有一个抽象层(枚举器)。我使用while
循环来编写for
,以类似的表示方式显示两个版本。
话虽如此…
你说的是执行时间上的细微差别。使用使代码更清晰和更小的循环,在这种情况下,它看起来像foreach
.
您没有在"for"测试之后重置"秒表",因此'for'测试中花费的时间被添加到随后的'foreach'测试中。此外,正如正确指定的那样,您应该在'for'中执行赋值操作,以模仿foreach的确切行为。
sw.Start();
foreach (var item in test)
{
}
sw.Stop();
Console.WriteLine("foreach " + sw.ElapsedMilliseconds);
sw.Restart();
for (int j = 0; j < test.Count; j++)
{
T temp = test[j];
}
sw.Stop();
Console.WriteLine("for " + sw.ElapsedMilliseconds);
sw.Reset(); // -- This bit is missing!
在您的for
周期中,我没有看到您实际访问test
中的项目。如果将var x = test[i];
添加到for
循环中,您将看到性能(实际上)是相同的。
每次访问值类型属性都会创建一个副本,要么使用foreach
,要么在for
循环中对list
使用indexer
。
这是一个关于为什么我应该使用foreach而不是for (int I =0;i<长度;i++)中的循环?
我认为foreach提供了一种抽象的循环方式,但它在技术上比for循环慢,在这里可以找到一篇关于for循环和foreach之间区别的好文章
你的测试不公平。考虑foreach
循环是如何操作的。您有以下代码:
foreach (var item in test)
{
}
这将创建一个变量item
,并在每次迭代中从集合中获取下一个对象,并将其分配给item
。这种获取和赋值不应该创建副本,但它确实需要时间来访问底层集合并将正确的值赋给变量。
那么你有这个代码:
for (int j = 0; j < test.Count; j++)
{
}
这根本不访问底层集合。它不会在每次迭代中读取和赋值变量。它只是将一个整数test.Count
加1次,所以它当然更快。如果编译器很聪明,它会发现循环中没有操作发生,然后把整个过程优化掉。
一个公平的比较应该将第二比特代码替换为如下内容:
var item;
for (int j = 0; j < test.Count; j++)
{
item = test.get(j);
}
这与你的foreach
循环所做的更相似。
至于使用哪一种,这实际上是个人偏好和编码风格的问题。从可读性的角度来看,我一般觉得foreach
比for(...)
更清晰。
我发现只有一种情况很重要——为Windows Phone 7开发。改变
有两个原因foreach(var item in colletion)
{
}
int length = array.Length;
for(int i = 0; i < length; ++i)
{
}
在XNA游戏中,如果收藏品很大,或者经常被调用(例如)。更新方法)。
- 它是快一点 <
- 少垃圾/gh>
和垃圾是至关重要的,因为Compact Framework GC每分配1MB就会触发一次,因此可能会导致令人讨厌的冻结。