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

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

例如,请参阅此答案中的以下代码。

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); } } 

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

 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 } 

你再次使用在条件中调用方法的副作用。 正如我所说,当你处理一个返回两位数据的方法时, 除了这个特殊模式之外 ,我没有养成这样做的习惯。

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

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

回到你原来的问题:如果一个变量将在循环的每次迭代中初始化并且它仅在循环体内使用,我几乎总是在循环内声明它。 这里的一个小例外是,如果变量被匿名函数捕获 – 此时它会改变行为,我会选择哪种forms给我所需的行为……但这几乎总是“声明里面“无论如何。

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

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

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

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

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

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

与许多这样的简单优化一样,编译器会为您处理它。 如果您尝试这两个并查看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) 

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

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

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

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

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