2010-07-13 91 views
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); 
    } 
} 

回答

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

我有一个范围的问题达成一致。当有人不得不阅读旧代码(或其他人的代码)时,最好有适当范围的变量。当我离开范围时,我不再担心该变量的最后一个值是什么。 – Torlack 2010-07-13 21:21:43

+0

@Torlack:我会编辑来解决这个问题。 – 2010-07-13 21:23:30

1

我通常优选后者为个人习惯问题,因为即使.NET是足够聪明,其他环境中,我稍后可能工作可能不够聪明。它可能只不过是编译成循环内的一行额外的代码来重新初始化变量,但它仍然是开销。

即使他们在任何给定的例子所有可测量的目的是相同的,我会说后者少造成的长远问题的机会。

+2

我不同意 - 因为你现在得到了一个比它需要的范围更大的变量,这对可读性通常是不利的。就我个人而言,我认为你应该适应你工作环境的习惯用语 - 如果你尝试以同样的方式编写C++,Java和C#,最终会遇到比这更大的问题。 – 2010-07-13 21:08:36

+0

够公平了,我绝对同意应该正确使用这些工具,而不是试图强迫它们看起来相似。我当然不想暗示其他情况。我想在这个特定的例子中,范围并不是什么大问题,因为无论如何它会立即结束。对于代码整体来说,肯定有很多需要说的,因为对于类似问题的所有情况,很少有全局解决方案。 – David 2010-07-13 21:19:42

3

在不可能的环境,不帮你这个问题,它仍然是一个微型优化。诸如清晰度和适当范围确定等因素比边缘情况要重要得多,因为这种情况可能几乎没有什么区别。

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

+0

+1即使在循环的每次迭代中都分配了变量,您可能也想在里面声明它。性能差异大部分时间可以忽略不计,但范围确定不是。 – helpermethod 2010-07-13 22:02:01

+0

我同意,我主要谈论的是在循环内部使用但未更改的变量,但是在循环之外声明并初始化,因为对象的初始化并不重要。正如Jon Skeet在他的回答中提到的那样,你可以引入一个新的范围来保持它在循环之外,但仍然适当的范围。这对于像资源句柄这样的东西非常适用,在这种情况下,您可以使用using(...)构造引入一个新范围。 – 2010-07-13 22:23:31

2

我会同意大多数其他答案与警告。

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

2

正如许多像这样的简单优化的情况一样,编译器会为您处理它。如果您尝试了以上两点,看看组件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) 
相关问题