Is it better to declare a variable inside or outside a loop?

后端 未结 6 1988
眼角桃花
眼角桃花 2020-12-01 07:55

Is better do:

variable1Type foo; 
variable2Type baa; 

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

    baa = new B         


        
6条回答
  •  抹茶落季
    2020-12-01 08:03

    Performance-wise, let's try concrete examples:

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

    I deliberately picked both a value-type and a reference-type in case that affects things. Now, the IL for them:

    .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 enumerator)
        L_0000: ldc.i4.0 
        L_0001: ldc.i4.s 10
        L_0003: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Range(int32, int32)
        L_0008: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1 [mscorlib]System.Collections.Generic.IEnumerable`1::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::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 enumerator)
        L_0000: ldc.i4.0 
        L_0001: ldc.i4.s 10
        L_0003: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Range(int32, int32)
        L_0008: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1 [mscorlib]System.Collections.Generic.IEnumerable`1::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::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
    }
    

    As you can see, apart from the order on the stack the compiler happened to choose - which could just as well have been a different order - it had absolutely no effect. In turn, there really isn't anything that one is giving the jitter to make much use of that the other isn't giving it.

    Other than that, there is one sort-of difference.

    In my Method1(), x and sb are scoped to the foreach, and cannot be accessed either deliberately or accidentally outside of it.

    In my Method2(), x and sb are not known at compile-time to be reliably assigned a value within the foreach (the compiler doesn't know the foreach will perform at least one loop), so use of it is forbidden.

    So far, no real difference.

    I can however assign and use x and/or sb outside of the foreach. As a rule I would say that this is probably poor scoping most of the time, so I'd favour Method1, but I might have some sensible reason to want to refer to them (more realistically if they weren't possibly unassigned), in which case I'd go for Method2.

    Still, that's a matter of how the each code can be extended or not, not a difference of the code as written. Really, there's no difference.

提交回复
热议问题