在.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"时,通过值类型的泛型集合迭代?

注:谢谢提醒,代码和结果是我编辑的。但是,每个人都比每个人跑得慢。

在.net中,使用"foreach"迭代IEnumerable<ValueType>会创建一个副本吗

你的问题太复杂了。分解。

使用"foreach"迭代值类型序列创建序列的副本吗?

使用"foreach"来迭代值类型序列是否创建每个值的副本?

是的。

使用"for"对值类型的索引序列进行等效迭代是否创建每个值的副本?

通常,是的。如果您知道关于集合的特殊情况,例如它是一个数组,那么您可以做一些事情来避免复制。但是在索引集合的一般情况下,索引序列返回序列中值的副本,而不是对包含值的存储位置的引用。

对值类型执行操作是否会复制该值?

差不多。值类型由值复制。这就是为什么它们被称为值类型。你对值类型所做的唯一不做复制的事情是调用值类型的方法,并使用"out"或"ref"传递值类型变量。值类型被不断复制 ;这就是值类型通常比引用类型慢的原因。

使用"foreach"或"for"来迭代引用类型的序列是否复制引用?

是的。引用类型表达式的值是引用。该引用在使用时被复制。

那么,就拷贝行为而言,值类型和引用类型有什么区别呢?

值类型按值复制。引用类型复制引用,但不复制被引用的对象。一个16字节的值类型每次使用它都会复制16个字节。16字节的引用类型在每次使用它时都会复制4(或8)字节的引用。

foreach循环比for循环慢吗?

通常是。foreach循环通常要做更多的工作,因为它要创建一个枚举数并调用枚举数上的方法,而不仅仅是增加一个整数。整数增量非常快。另外,不要忘记foreach循环中的枚举数必须被处理掉,这也需要花费时间。

我是否应该使用for循环而不是foreach循环,因为for循环有时会快几微秒?

。这是愚蠢的。您应该根据以客户为中心的经验数据做出明智的工程决策。foreach循环的额外负担很小。客户可能永远不会注意到。你应该做的是:

  • 根据客户输入设定绩效目标
  • 衡量你是否达到了你的目标
  • 如果你没有,找到最慢的东西使用分析器
  • 修复它
  • 重复,直到达到你的目标
如果您有性能问题,将foreach循环更改为for循环对您的问题没有任何影响,这是极有可能的。首先按照看起来清晰易懂的方式编写代码。

您的测试不准确;在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实现中的代码更少,而且foreachfor之上有一个抽象层(枚举器)。我使用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循环所做的更相似。

至于使用哪一种,这实际上是个人偏好和编码风格的问题。从可读性的角度来看,我一般觉得foreachfor(...)更清晰。

我发现只有一种情况很重要——为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就会触发一次,因此可能会导致令人讨厌的冻结。

相关文章: