声明在循环之外的循环中使用的变量而不是内部更好吗?有时我会看到在循环中声明变量的示例。这是否有效地导致程序在每次循环运行时为新变量分配内存?或者.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);
}
}
答案 0 :(得分:9)
不,它不会更有效率。但是,我会以这种方式重写它,无论如何都会在循环之外声明它:
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)
{
...
}
回到原来的问题:如果一个变量将在循环和的每次迭代中初始化,它只在循环体内使用,我几乎总是声明它循环内。这里的一个小例外是,如果变量被匿名函数捕获 - 此时它会改变行为,我会选择任何形式给我所需的行为......但这几乎总是“声明里面“无论如何。”
编辑:当涉及范围界定时,上面的代码确实将变量放在比它需要的范围更大的范围内......但我相信它使循环更清晰。如果您愿意,可以通过引入新的范围来解决这个问题:
{
int read;
while (...)
{
}
}
答案 1 :(得分:3)
在不太可能对你有帮助的环境中,它仍然是微观优化。清晰度和适当范围等因素比边缘情况要重要得多,而边缘情况可能只会产生差异。
您应该在不考虑性能的情况下为变量提供适当的范围。当然,复杂的初始化是一种不同的野兽,所以如果某些东西只应初始化一次但只在循环中使用,你仍然希望在外面声明它。
答案 2 :(得分:2)
我将同意其中大部分其他答案并提出警告。
如果您正在使用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个循环都指向同一个引用,在第二个例子中,它们都指向不同的引用。
答案 3 :(得分:2)
与许多这样的简单优化一样,编译器会为您处理它。如果您尝试这两个并查看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)
答案 4 :(得分:1)
我一般更喜欢后者是个人习惯的问题,因为即使.NET足够聪明,我以后可能会工作的其他环境也可能不够智能。它可能只是编译到循环内部的额外代码行来重新初始化变量,但它仍然是开销。
即使它们在任何给定的例子中对于所有可测量的目的都是相同的,但我认为后者在长期内不太可能造成问题。
答案 5 :(得分:1)
这没关系,如果我正在审查该特定示例的代码,我不会在意。
但是,请注意,如果你最终在闭包中捕获'read'变量,这两者可能意味着非常不同。
请参阅Eric Lippert发表的关于foreach循环的问题的优秀文章 - http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx