在foreach循环的内部或外部声明变量:哪个更快/更好?


92

哪个更快/更好?

这个:

List<User> list = new List<User>();
User u;

foreach (string s in l)
{
    u = new User();
    u.Name = s;
    list.Add(u);
}

或者这个:

List<User> list = new List<User>();

foreach (string s in l)
{
    User u = new User();
    u.Name = s;
    list.Add(u);
}

我的新手开发技能告诉我第一个更好,但是我的一个朋友告诉我,我错了,但是不能给我第二个更好的理由。

表现上有什么区别吗?

Answers:


112

在性能方面,两个示例均编译为相同的IL,因此没有区别。

第二个更好,因为如果u仅在循环内使用它,则可以更清楚地表达您的意图。


10
请注意,如果变量是由lambda表达式或匿名委托捕获的,有所不同。请参阅外部变量陷阱
dtb

您能解释为什么两者都编译为相同的IL吗?我可以肯定C#不会像javascript那样将变量声明提升到函数的顶部。
styfle

4
@styfle这是您问题的答案
David Sherret 2014年

以下堆栈溢出链接提供了更详细的答案:1) 乔恩·汉娜Jon Hanna)2) StriplingWarrior
user3613932

14

无论如何,最好的方法是使用带有Name ...的构造函数,否则,利用花括号符号:

foreach (string s in l)
{
    list.Add(new User(s));
}

要么

foreach (string s in l)
{
    list.Add(new User() { Name = s });
}

甚至更好,LINQ:

var list = l.Select( s => new User { Name = s});

现在,虽然您的第一个示例在某些情况下可能会感觉上更快,但是第二个示例则更好,因为它更具可读性,并且编译器可能会丢弃该变量(并完全忽略它),因为它的使用foreach范围不超过s的范围。


6
当天的恋人评论:“甚至更好,LINQ”。当然,这只是一行代码,这使我们作为开发人员感到不错。但是四行版本的FAR更易于理解,因此可以维护。
Oskar Austegard

5
几乎不。有了LINQ版本,我知道我的工作是不可变的,并且可以处理所有元素。
Tordek

6

声明不会导致任何代码被执行,因此这不是性能问题。

第二个是您的意思,如果以第二种方式执行此操作,则您不太可能犯一个愚蠢的错误,因此请使用该错误。始终尝试在必要的最小范围内声明变量。

此外,更好的方法是使用Linq:

List<User> users = l.Select(name => new User{ Name = name }).ToList();

2
我喜欢“总是尝试在必要的最小范围内声明变量”这一行。我认为一行可以很好地回答这个问题。
Manjoor

5

每当您对性能有疑问时,唯一要做的就是测量-在测试周围运行一个循环并计时。

要回答您的问题-无需测量:-)或查看生成的问题-在有意义的迭代次数和代码中最昂贵的操作中,任何差异都不会明显,可能是用户分配了几个命令因此,请专注于代码的清晰度(通常应该如此),然后选择2。

哦,太晚了,我想我只是想说不要担心这种事情,也不要陷入诸如此类的细节中。

ķ


谢谢,我想我还会再谈一些我一直在苦恼的东西了:D
Marcus

如果要进一步研究影响性能的因素,请使用代码分析器。如果没有其他要求,它将开始让您了解哪种类型的代码和操作花费最多的时间。ProfileSharp和EqatecProfilers是免费的,足以帮助您入门。
凯文·谢伊


1

从技术上讲,第一个示例将节省几纳秒的时间,因为不必移动堆栈框架来分配新的变量,但是这是一个很小的CPU时间,您不会注意到它,也就是说,如果编译器没有优化任何差异。


我很确定CLR不会在循环的每次迭代中分配“新变量”。
dtb

好的,编译器可能会优化它,但是必须为循环中的任何变量分配堆栈空间。这将取决于实现,一个实现可以简单地使栈帧保持相同,而另一个实现(例如Mono)可以释放栈,然后在每个循环上重新创建栈。
Erik Funkenbusch,2009年

16
方法中的所有局部变量(顶级或嵌套在循环中)都将编译为IL中的方法级变量。在执行方法之前分配变量的空间,而不是在到达带有C#中的声明的分支时分配的空间。
dtb

1
@dtb您是否有此申诉的消息来源?
styfle 2014年

1

在这种情况下,第二个版本更好。

通常,如果只需要访问迭代主体中的值,则选择第二个版本。另一方面,如果存在某些最终状态,则变量将保持在循环主体之外,然后声明然后使用第一个版本。




0

我去验证了这个问题。出乎意料的是,在我的肮脏测试中,发现第二个选项始终比以前稍快一些。

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    void Method1()
    {
      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }
    }

    void Method2()
    {

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }
    }
  }

  public class User { public string Name; }
}

我验证了CIL,但不完全相同。

在此处输入图片说明

因此,我准备了一些我希望得到更好测试的东西。

namespace Test
{
  class Loop
  { 

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    public void Method1()
    {
      sw.Restart();

      C c;
      C c1;
      C c2;
      C c3;
      C c4;

      int i = 1000;
      while (i-- > 0)
      {
        c = new C();
        c1 = new C();
        c2 = new C();
        c3 = new C();
        c4 = new C();        
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    public void Method2()
    {
      sw.Restart();

      int i = 1000;
      while (i-- > 0)
      {
        var c = new C();
        var c1 = new C();
        var c2 = new C();
        var c3 = new C();
        var c4 = new C();
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  class C { }
}

同样在这种情况下,第二种方法始终是赢家,但后来我验证了CIL,发现没有区别。

在此处输入图片说明

我不是阅读CIL的专家,但我看不到任何退位问题。正如已经指出的那样,声明不是分配,因此不存在性能损失。

测试

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    void Method1()
    {
      sw.Restart();

      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    void Method2()
    {
      sw.Restart();

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  public class User { public string Name; }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.