C# 变量声明是否应始终放在循环之外?

C# 变量声明是否应始终放在循环之外?,c#,.net,loops,variable-declaration,C#,.net,Loops,Variable Declaration,在循环外部而不是内部声明循环中使用的变量是否更好?有时我会看到在循环中声明变量的示例。这是否有效地导致程序在每次循环运行时为新变量分配内存?或者.NET是否足够聪明,知道它实际上是同一个变量 例如,请参见下面的代码 publicstaticvoidcopystream(流输入、流输出) { 字节[]缓冲区=新字节[32768]; while(true) { int read=input.read(buffer,0,buffer.Length); 如果(readNo,则效率不会更高。但是,我会以这

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

例如,请参见下面的代码

publicstaticvoidcopystream(流输入、流输出)
{
字节[]缓冲区=新字节[32768];
while(true)
{
int read=input.read(buffer,0,buffer.Length);

如果(readNo,则效率不会更高。但是,我会以这种方式重写它,这恰好在循环之外声明了它:

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
}
同样,在条件中调用该方法会产生副作用。正如我所说,当处理返回两位数据的方法时,我不会养成这样做的习惯,除了这个特殊的模式

文本阅读器
读取行时也会出现同样的情况:

string line;
while ((line = reader.ReadLine()) != null)
{
    ...
}
回到你最初的问题:如果一个变量将在循环的每次迭代中初始化,并且它只在循环体中使用,我几乎总是在循环中声明它。这里的一个小例外是,如果该变量被匿名函数捕获,那么在这一点上,它将在行为上产生不同,一个我会选择任何一种给我想要的行为的形式……但无论如何,这几乎总是“内部声明”形式

编辑:当涉及到作用域时,上面的代码确实使变量的作用域比它需要的更大……但我相信它使循环更清晰。如果您愿意,您可以通过引入一个新的作用域来解决这个问题:

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

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


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

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

但是,请注意,如果您最终在闭包中捕获“read”变量,那么这两个变量可能意味着非常不同的事情


请参阅Eric Lippert的这篇优秀文章,其中提到了有关foreach循环的问题-

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


您应该在不考虑性能的情况下为变量指定适当的范围。当然,复杂的初始化是另一回事,因此,如果某个变量只应初始化一次,但仅在循环中使用,您仍然希望在外部声明它。

我同意大多数其他答案,但有一点需要注意

如果使用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
1
2
3
在哪里

将给出结果

3
3
3
1
2
3

这是因为当任务最终启动时,它将检查它对x的引用的当前值。在第一个示例中,所有3个循环都指向同一个引用,而在第二个示例中,它们都指向不同的引用。

与许多类似的简单优化一样,编译器采用ca如果您同时尝试这两种方法,并查看ildasm中程序集的IL,您可以看到它们都声明了一个int32 read变量,尽管它会对声明进行重新排序:

  .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)

我不同意——因为你现在得到了一个比它需要的范围更大的变量,这通常对可读性很不利。我个人认为你应该适应你所处环境的习语——如果你尝试用C++方式,java和C语言一样,你会遇到比这更大的问题。人们不应该正确地使用这些工具,而应该试图强迫它们看起来像。我当然不想暗示其他。我想在这个特定的例子中,范围并不是什么大问题,因为它会在之后立即结束。对于整个代码的上下文来说,肯定有很多话要说,因为很少有全局sol我必须同意范围问题。当有人必须阅读旧代码(或其他人的代码)时,拥有具有适当作用域的变量很好。当我离开作用域时,我不再需要担心该变量的最后一个值可能是什么。+1即使在循环的每次迭代中都分配了该变量,您也可能希望在内部声明它。性能差异在大多数情况下可以忽略,但作用域不是。我同意,我主要讨论的是在循环内使用但未更改,但在循环外声明和初始化的变量,因为对象的初始化非常重要。正如Jon Skeet在他的回答中提到的,您可以引入一个新的作用域,使其保持在循环外,但仍然具有适当的作用域。这对stuff l非常有效ike资源句柄,在这种情况下,可以使用(…)构造引入新的作用域。
  .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)