首页 文章

变量声明是否应该放在循环之外?

提问于
浏览
9

声明在循环外部的循环中使用的变量而不是内部更好吗?有时我会看到在循环中声明变量的示例 . 这是否有效地导致程序在每次循环运行时为新变量分配内存?或者.NET足够聪明,知道它真的是同一个变量 .

例如,请参阅下面的代码this answer .

public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[32768];
    while (true)
    {
        int read = input.Read (buffer, 0, buffer.Length);
        if (read <= 0)
            return;
        output.Write (buffer, 0, read);
    }
}

这个修改过的版本会更有效吗?

public static void CopyStream(Stream input, Stream output)
{
    int read; //OUTSIDE LOOP
    byte[] buffer = new byte[32768];
    while (true)
    {
        read = input.Read (buffer, 0, buffer.Length);
        if (read <= 0)
            return;
        output.Write (buffer, 0, read);
    }
}

6 回答

  • 9

    不,它不会更有效率 . 但是,我会以这种方式重写它,无论如何都会在循环之外声明它:

    byte[] buffer = new byte[32768];
    int read;
    while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, read);
    }
    

    我通常不喜欢在条件中使用副作用,但实际上 Read 方法会给你两位数据:是否_257668_已读 . while循环现在说,"While we've managed to read some data... copy it."

    这有点像使用 int.TryParse

    if (int.TryParse(text, out value))
    {
        // Use value
    }
    

    当你处理一个返回两位数据的方法时,除了这个特殊的模式之外,你再次做出这样的习惯 .

    同样的事情是从 TextReader 读取行:

    string line;
    while ((line = reader.ReadLine()) != null)
    {
        ...
    }
    

    回到原来的问题:如果变量将在循环的每次迭代中初始化,并且它几乎总是在循环内声明它 . 这里的一个小异常是,如果变量被匿名函数捕获 - 那时它会对行为产生影响,而且我仍然总是"declare inside"形式 .

    编辑:当涉及范围界定时,上面的代码确实将变量放在一个比它需要的范围更大的范围内...但我相信它使循环更清晰 . 如果您愿意,可以通过引入新范围来解决此问题:

    {
        int read;
        while (...)
        {
        }
    }
    
  • 2

    在不太可能对您有帮助的环境中,它仍然是微观优化 . 清晰度和适当范围等因素比边缘情况要重要得多,而边缘情况可能只会产生差异 .

    您应该在不考虑性能的情况下为变量提供适当的范围 . 当然,复杂的初始化是一个不同的野兽,所以如果某些东西只应初始化一次但只在循环中使用,你仍然希望在外面声明它 .

  • 1

    我将同意其中大部分其他答案并提出警告 .

    如果您正在使用lambada表达式,则必须小心捕获变量 .

    static void Main(string[] args)
    {
        var a = Enumerable.Range(1, 3);
        var b = a.GetEnumerator();
        int x;
        while(b.MoveNext())
        {
            x = b.Current;
            Task.Factory.StartNew(() => Console.WriteLine(x));
        }
        Console.ReadLine();
    }
    

    会给出结果

    3
    3
    3
    

    哪里

    static void Main(string[] args)
    {
        var a = Enumerable.Range(1, 3);
        var b = a.GetEnumerator();
        while(b.MoveNext())
        {
            int x = b.Current;
            Task.Factory.StartNew(() => Console.WriteLine(x));
        }
        Console.ReadLine();
    }
    

    会给出结果

    1
    2
    3
    

    或某些订单 . 这是因为当任务最终启动时,它将检查它对x的引用的当前值 . 在第一个例子中,所有3个循环都指向相同的引用,在第二个例子中,它们都指向不同的引用 .

  • 1

    与许多这样的简单优化一样,编译器会为您处理它 . 如果您尝试这两个并查看ildasm中的程序集IL,您可以看到它们都声明了一个int32读取变量,尽管它确实重新排序了声明:

    .locals init ([0] int32 read,
               [1] uint8[] buffer,
               [2] bool CS$4$0000)
    
      .locals init ([0] uint8[] buffer,
               [1] int32 read,
               [2] bool CS$4$0000)
    
  • 3

    我一般更喜欢后者是个人习惯的问题,因为即使.NET足够聪明,我以后可能工作的其他环境也可能不够聪明 . 它可能只是编译到循环内部的额外代码行来重新初始化变量,但它仍然是开销 .

    即使它们在任何给定的例子中对于所有可测量的目的都是相同的,但我认为后者在长期内不太可能引起问题 .

  • 2

    这真的没关系,如果我正在审查该特定示例的代码,我不会在意任何一种方式 .

    但是,请注意,如果您最终在闭包中捕获“读取”变量,则这两者可能意味着完全不同的事情 .

    请参阅Eric Lippert的这篇优秀文章,其中关于foreach循环的问题出现了 - http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

相关问题