【问题标题】:Should variable declarations always be placed outside of a loop?变量声明是否应该始终放在循环之外?
【发布时间】:2010-07-13 21:00:01
【问题描述】:

在循环外而不是在循环内声明在循环中使用的变量是否更好?有时我会看到在循环内声明变量的示例。这是否有效地导致程序在每次循环运行时为新变量分配内存?或者 .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);
    }
}

【问题讨论】:

    标签: c# .net loops variable-declaration


    【解决方案1】:

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

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

    我通常不喜欢在条件下使用副作用,但实际上Read 方法会为您提供两位数据:您是否已到达流的末尾,以及您的数量读过。 while 循环现在说,“虽然我们已经设法读取了一些数据……但复制它。”

    有点像使用int.TryParse

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

    您再次使用了在条件中调用方法的副作用。正如我所说,当您处理返回两位数据的方法时,我不会养成这样做的习惯except

    TextReader 中读取行也会出现同样的情况:

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

    回到你原来的问题:如果要在循环的每次迭代中初始化一个变量并且它只在循环体中使用,我几乎总是声明它循环内。这里的一个小例外是,如果变量被匿名函数捕获 - 那时它会改变行为,我会选择任何一种形式给我想要的行为......但这几乎总是“内部声明” " 无论如何都要形成。

    编辑:说到范围,上面的代码确实使变量的范围比它需要的更大……但我相信它使循环更清晰。如果您愿意,您始终可以通过引入新范围来解决此问题:

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

    【讨论】:

    • 我必须同意范围问题。当有人不得不阅读旧代码(或其他人的代码)时,最好有适当范围的变量。当我离开作用域时,我不再需要担心该变量的最后一个值可能是什么。
    【解决方案2】:

    在对您没有帮助的不太可能的环境中,它仍然是一个微优化。清晰度和适当的范围界定等因素比边缘情况重要得多,边缘情况可能几乎没有区别。

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

    【讨论】:

    • +1 即使在循环的每次迭代中都分配了变量,您也可能希望在其中声明它。大多数情况下,性能差异可以忽略不计,但范围界定则不然。
    • 我同意,我主要是在谈论在循环内使用但未更改,但在循环外声明和初始化的变量,因为对象的初始化是不平凡的。正如 Jon Skeet 在他的回答中提到的那样,您可以引入一个新的范围以将其保持在循环之外,但仍然具有适当的范围。这对资源句柄之类的东西非常有效,在这种情况下,您可以使用 using(...) 构造引入一个新范围。
    【解决方案3】:

    我将同意大多数其他答案,但需要注意的是。

    如果您使用 lambda 表达式,则必须小心捕获变量。

    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 个循环都指向同一个引用,而在第二个示例中,它们都指向不同的引用。

    【讨论】:

      【解决方案4】:

      与许多像这样的简单优化一样,编译器会为您处理这些问题。如果您尝试这两种方法并在 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)
      

      【讨论】:

        【解决方案5】:

        出于个人习惯,我通常更喜欢后者,因为即使 .NET 足够智能,我以后可能工作的其他环境也可能不够智能。可能只不过是在循环内编译成一行额外的代码来重新初始化变量,但这仍然是开销。

        即使在任何给定示例中它们在所有可衡量的目的上都是相同的,我会说后者从长远来看引起问题的可能性较小。

        【讨论】:

        • 我不同意 - 因为你现在有一个范围比它需要的更大的变量,这通常不利于可读性。就我个人而言,我认为你应该适应你工作环境的习惯用法——如果你尝试用同样的方式编写 C++、Java 和 C#,你最终会遇到比这更大的问题。
        • 很公平,我绝对同意应该正确使用这些工具,而不是试图强迫它们看起来相似。我当然不想暗示其他。我想在这个特定的示例中,范围并不是什么大问题,因为无论如何它都会立即结束。对于整个代码的上下文,肯定有很多话要说,因为很少有针对所有类似问题实例的全局解决方案。
        【解决方案6】:

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

        但是,请注意,如果您最终在闭包中捕获“读取”变量,这两者可能意味着非常不同的东西。

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

        【讨论】:

          猜你喜欢
          • 2012-02-17
          • 1970-01-01
          • 1970-01-01
          • 2015-04-12
          • 1970-01-01
          • 2014-03-24
          • 2016-06-07
          • 2022-11-04
          • 1970-01-01
          相关资源
          最近更新 更多