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

时间:2022-05-27 00:23:57

Is it better to declare a variable used in a loop outside of the loop rather then inside? Sometimes I see examples where a variable is declared inside the loop. Does this effectively cause the program to allocate memory for a new variable each time the loop runs? Or is .NET smart enough to know that it's really the same variable.

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

For example see the code below from 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);
    }
}

Would this modified version be any more efficent?

这个修改过的版本会更有效吗?

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

6 个解决方案

#1


9  

No, it wouldn't be more efficient. However, I'd rewrite it this way which happens to declare it outside the loop anyway:

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

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

I'm not generally a fan of using side-effects in conditions, but effectively the Read method is giving you two bits of data: whether or not you've reached the end of the stream, and how much you've read. The while loop is now saying, "While we've managed to read some data... copy it."

我通常不喜欢在条件中使用副作用,但有效的Read方法会给你两位数据:你是否已经到达流的末尾,以及你读了多少。 while循环现在说,“虽然我们设法读取了一些数据......但要复制它。”

It's a little bit like using int.TryParse:

这有点像使用int.TryParse:

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

Again you're using a side-effect of calling the method in the condition. As I say, I don't make a habit out of doing this except for this particular pattern, when you're dealing with a method returning two bits of data.

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

The same thing comes up reading lines from a TextReader:

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

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

To go back to your original question: if a variable is going to be initialized in every iteration of a loop and it's only used within the body of the loop, I'd almost always declare it within the loop. One minor exception here is if the variable is being captured by an anonymous function - at that point it will make a difference in behaviour, and I'd pick whichever form gave me the desired behaviour... but that's almost always the "declare inside" form anyway.

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

EDIT: When it comes to scoping, the code above does indeed leave the variable in a larger scope than it needs to be... but I believe it makes the loop clearer. You can always address this by introducing a new scope if you care to:

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

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

#2


3  

In the unlikely environment that doesn't help you with this, it would still be a micro-optimization. Factors like clarity and proper scoping is much more important than the edge case where this might just make next to no difference.

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

You should give your variables proper scope without thinking about performance. Of course, complex initializations are a different beast, so if something should only be initialized once but is only used within a loop, you'd still want to declare it outside.

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

#3


2  

I am going to agree with most of these other answers with a caveat.

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

If you are using lambada expressions you must be careful with capturing variables.

如果您使用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();
}

will give the result

会给出结果

3
3
3

Where

哪里

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

will give the result

会给出结果

1
2
3

or some order there of. This is because when the task finally starts it will check the current value of it's reference to x. in the first example all 3 loops pointed at the same reference, where in the second example they all pointed at different references.

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

#4


2  

As is the case with lots of simple optimizations like this, the compiler takes care of it for you. If you try both of these and look at the assemblies' IL in ildasm you can see that they both declare a single int32 read variable, although it does reorder the declarations:

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


1  

I've generally preferred the latter as a matter of personal habit because, even if .NET is smart enough, other environments in which I might work later may not be smart enough. It could be nothing more than compiling down to an extra line of code inside the loop to re-initialize the variable, but it's still overhead.

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

Even if they're identical for all measurable purposes in any given example, I would say the latter has less of a chance of causing problems in the long run.

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

#6


1  

It really doesn't matter, and if I was reviewing the code for that particular example, I wouldn't care either way.

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

However, be aware that the two can mean very different things if you end up capturing the 'read' variable in a closure.

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

See this excellent post from Eric Lippert where this issue comes up regarding foreach loops - http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

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

#1


9  

No, it wouldn't be more efficient. However, I'd rewrite it this way which happens to declare it outside the loop anyway:

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

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

I'm not generally a fan of using side-effects in conditions, but effectively the Read method is giving you two bits of data: whether or not you've reached the end of the stream, and how much you've read. The while loop is now saying, "While we've managed to read some data... copy it."

我通常不喜欢在条件中使用副作用,但有效的Read方法会给你两位数据:你是否已经到达流的末尾,以及你读了多少。 while循环现在说,“虽然我们设法读取了一些数据......但要复制它。”

It's a little bit like using int.TryParse:

这有点像使用int.TryParse:

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

Again you're using a side-effect of calling the method in the condition. As I say, I don't make a habit out of doing this except for this particular pattern, when you're dealing with a method returning two bits of data.

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

The same thing comes up reading lines from a TextReader:

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

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

To go back to your original question: if a variable is going to be initialized in every iteration of a loop and it's only used within the body of the loop, I'd almost always declare it within the loop. One minor exception here is if the variable is being captured by an anonymous function - at that point it will make a difference in behaviour, and I'd pick whichever form gave me the desired behaviour... but that's almost always the "declare inside" form anyway.

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

EDIT: When it comes to scoping, the code above does indeed leave the variable in a larger scope than it needs to be... but I believe it makes the loop clearer. You can always address this by introducing a new scope if you care to:

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

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

#2


3  

In the unlikely environment that doesn't help you with this, it would still be a micro-optimization. Factors like clarity and proper scoping is much more important than the edge case where this might just make next to no difference.

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

You should give your variables proper scope without thinking about performance. Of course, complex initializations are a different beast, so if something should only be initialized once but is only used within a loop, you'd still want to declare it outside.

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

#3


2  

I am going to agree with most of these other answers with a caveat.

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

If you are using lambada expressions you must be careful with capturing variables.

如果您使用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();
}

will give the result

会给出结果

3
3
3

Where

哪里

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

will give the result

会给出结果

1
2
3

or some order there of. This is because when the task finally starts it will check the current value of it's reference to x. in the first example all 3 loops pointed at the same reference, where in the second example they all pointed at different references.

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

#4


2  

As is the case with lots of simple optimizations like this, the compiler takes care of it for you. If you try both of these and look at the assemblies' IL in ildasm you can see that they both declare a single int32 read variable, although it does reorder the declarations:

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


1  

I've generally preferred the latter as a matter of personal habit because, even if .NET is smart enough, other environments in which I might work later may not be smart enough. It could be nothing more than compiling down to an extra line of code inside the loop to re-initialize the variable, but it's still overhead.

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

Even if they're identical for all measurable purposes in any given example, I would say the latter has less of a chance of causing problems in the long run.

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

#6


1  

It really doesn't matter, and if I was reviewing the code for that particular example, I wouldn't care either way.

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

However, be aware that the two can mean very different things if you end up capturing the 'read' variable in a closure.

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

See this excellent post from Eric Lippert where this issue comes up regarding foreach loops - http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

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