在循环内部或外部声明变量是否更好?

时间:2011-12-16 14:51:32

标签: c# .net scope

做得更好:

variable1Type foo; 
variable2Type baa; 

foreach(var val in list) 
{
    foo = new Foo( ... ); 
    foo.x = FormatValue(val); 

    baa = new Baa(); 
    baa.main = foo; 
    baa.Do();
}

或者:

foreach(var val in list) 
{
    variable1Type foo = new Foo( ... ); 
    foo.x = FormatValue(val); 

    variable2Type baa = new Baa(); 
    baa.main = foo; 
    baa.Do();
}

问题是:什么是更快的1例或2例? 区别是微不足道的吗?它在实际应用中是一样的吗? 这可能是一个优化微观,但我真的想知道哪个更好。

6 个答案:

答案 0 :(得分:52)

在性能方面,让我们尝试具体的例子:

public void Method1()
{
  foreach(int i in Enumerable.Range(0, 10))
  {
    int x = i * i;
    StringBuilder sb = new StringBuilder();
    sb.Append(x);
    Console.WriteLine(sb);
  }
}
public void Method2()
{
  int x;
  StringBuilder sb;
  foreach(int i in Enumerable.Range(0, 10))
  {
    x = i * i;
    sb = new StringBuilder();
    sb.Append(x);
    Console.WriteLine(sb);
  }
}

我故意选择了值类型和引用类型,以防影响事物。现在,他们的IL:

.method public hidebysig instance void Method1() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] int32 x,
        [2] class [mscorlib]System.Text.StringBuilder sb,
        [3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> enumerator)
    L_0000: ldc.i4.0 
    L_0001: ldc.i4.s 10
    L_0003: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32)
    L_0008: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
    L_000d: stloc.3 
    L_000e: br.s L_002f
    L_0010: ldloc.3 
    L_0011: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
    L_0016: stloc.0 
    L_0017: ldloc.0 
    L_0018: ldloc.0 
    L_0019: mul 
    L_001a: stloc.1 
    L_001b: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_0020: stloc.2 
    L_0021: ldloc.2 
    L_0022: ldloc.1 
    L_0023: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(int32)
    L_0028: pop 
    L_0029: ldloc.2 
    L_002a: call void [mscorlib]System.Console::WriteLine(object)
    L_002f: ldloc.3 
    L_0030: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    L_0035: brtrue.s L_0010
    L_0037: leave.s L_0043
    L_0039: ldloc.3 
    L_003a: brfalse.s L_0042
    L_003c: ldloc.3 
    L_003d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0042: endfinally 
    L_0043: ret 
    .try L_000e to L_0039 finally handler L_0039 to L_0043
}

.method public hidebysig instance void Method2() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 x,
        [1] class [mscorlib]System.Text.StringBuilder sb,
        [2] int32 i,
        [3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> enumerator)
    L_0000: ldc.i4.0 
    L_0001: ldc.i4.s 10
    L_0003: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32)
    L_0008: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
    L_000d: stloc.3 
    L_000e: br.s L_002f
    L_0010: ldloc.3 
    L_0011: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
    L_0016: stloc.2 
    L_0017: ldloc.2 
    L_0018: ldloc.2 
    L_0019: mul 
    L_001a: stloc.0 
    L_001b: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_0020: stloc.1 
    L_0021: ldloc.1 
    L_0022: ldloc.0 
    L_0023: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(int32)
    L_0028: pop 
    L_0029: ldloc.1 
    L_002a: call void [mscorlib]System.Console::WriteLine(object)
    L_002f: ldloc.3 
    L_0030: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    L_0035: brtrue.s L_0010
    L_0037: leave.s L_0043
    L_0039: ldloc.3 
    L_003a: brfalse.s L_0042
    L_003c: ldloc.3 
    L_003d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0042: endfinally 
    L_0043: ret 
    .try L_000e to L_0039 finally handler L_0039 to L_0043
}

正如你所看到的,除了编译器碰巧选择的堆栈顺序 - 这可能只是一个不同的顺序 - 它完全没有效果。反过来,实际上没有任何东西可以让抖动充分利用另一个人没有给予它。

除此之外,还有一种区别。

在我的Method1()中,xsb的范围限定为foreach,无法在其外部故意或意外访问。

在我的Method2()中,xsb在编译时未知在foreach内可靠地分配值(编译器不知道<{1}}将执行至少一个循环),因此禁止使用它。

到目前为止,没有真正的区别。

可以分配并使用foreach之外的x和/或sb。作为一项规则,我会说这可能在大多数情况下都很糟糕,所以我赞成foreach,但我可能有一些合理的理由想要引用它们(如果它们不可能则更现实一些)未分配的),在这种情况下,我会去Method1

但是,这是每个代码如何扩展的问题,而不是编写代码的差异。真的,没有区别。

答案 1 :(得分:4)

没关系,它对性能没有任何影响。

  

但我真的想知道做正确的方法。

大多数人会告诉你内部循环最有意义。

答案 2 :(得分:3)

这只是一个范围问题。在这种情况下,foo和baa仅在for循环中使用,最佳做法是在循环内声明它们。它也更安全。

答案 3 :(得分:1)

好的,我没有注意到原始海报每次在循环中创建一个新对象而不只是“使用”该对象时回答了这个问题。所以,不,在性能方面,不应该有一个微不足道的差异。有了这个,我会使用第二种方法并在循环中声明对象。这样,它将在下一次GC传递期间被清理,并将对象保持在范围内。

---我会留下我的原始答案,因为我输入了所有内容,这可能会帮助后来搜索此消息的其他人。我保证,在我尝试回答下一个问题之前,我会更加关注。

实际上,我认为存在差异。如果我没记错,每次创建新对象= new foo()时,都会将该对象添加到内存堆中。因此,通过在循环中创建对象,您将增加系统的开销。如果你知道循环会很小,那就不是问题了。

所以,如果你最终在一个包含1000个对象的循环中,你将创建1000个变量,这些变量在下一个垃圾收集之前不会被处理掉。现在点击一个你想要做某事的数据库,你有20,000多行可以使用...你可以根据你正在创建的对象类型创建相当大的系统需求。

这应该很容易测试...创建一个应用程序,当您进入循环和退出时创建带有时间戳的10,000个项目。第一次这样做时,在循环之前和循环期间的下一次声明变量。你可能不得不将这个计数提高到远高于10K才能看到真正的速度差异。

同样,还有范围问题。如果在循环中创建,则一旦退出循环就会消失,因此您无法再次访问它。但这也有助于清理,因为垃圾收集一旦退出循环就会最终处理它。

答案 4 :(得分:0)

两者都是完全有效的,不确定是否有'正确的方法'来做到这一点。

您的第一个案例更节省内存(至少在短期内)。在循环中声明变量将强制更多地重新分配内存;但是,对于.NETs垃圾收集器,由于这些变量超出范围,因此它们将定期清理,但不一定立即清理。速度的差异可以忽略不计。

第二种情况确实有点安全,因为尽可能地限制变量的范围通常是很好的做法。

答案 5 :(得分:0)

在JS中,内存分配每次都是完整的,在C#中,通常没有这样的差异,但是如果局部变量被像lambda表达式这样的匿名方法捕获,那么它就很重要。