Is it better to declare a variable inside or outsi

2019-01-08 18:56发布

问题:

Is better do:

variable1Type foo; 
variable2Type baa; 

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

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

Or:

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

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

The question is: What is faster 1 case or 2 case? Is the difference is insignificant? Is it the same in real applications? This may be a optimization-micro, but I really want know which is better.

回答1:

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

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.



回答2:

It doesn't matter, it has no effect on performance whatsoever.

but I really want know to do right way.

Most will tell you inside-the-loop makes the most sense.



回答3:

It's just a matter of scope. In this case, where foo and baa are only used within the for loop, it's best practice to declare them inside the loop. It's safer too.



回答4:

OK, I answered this without noticing where the original poster was creating a new object every time going through the loop and not just 'using' the object. So, no, there should not really be a negligible difference when it comes to performance. With this, I would go with the second method and declare the object within the loop. That way it will be cleaned up during the next GC pass and the maintains the object within the scope.

--- I will leave up my orignal answer, just because I typed it all, and it might help someone else who searches this message later on. I promise that in the future, I will pay more attention before I try and answer the next question.

Tim

Actually, I think there is a difference. If I recall correctly, every time you create a new object = new foo() you will get that object added to the memory heap. So, by creating the objects in the loop you are going to be adding to the overhead of the system. If you know that the loop is going to be small, it's not a problem.

So, if you end up in a loop with say 1000 objects in it, you are going to be creating 1000 variables that will not be disposed of until the next garbage collection. Now hit a database where you want to do something and you have 20,000+ rows to work with... You can create quite a system demand depending on what object type you are creating.

This should be easy to test... Create an app that creates 10,000 items with a time stamp when you enter the loop and when you exit. The first time you do it, declare the variable before the loop and the next time during the loop. You might have to bump that count up much higher than 10K to see a real difference in speed though.

As well, there is the scope issue. If created in the loop, it's gone once you exit the loop, so you can't access it again. But this also helps with the clean up as the garbage collection will eventually dispose of it once it exit the loop.

Tim



回答5:

Both are completely valid, not sure there's a 'right way' to do this.

Your first case is more memory-efficient (at least in the short term). Declaring your variables inside the loop will force more reallocation of memory; however, with .NETs garbage collector, as those variables go out of scope they will be cleaned up periodically, but not necessarily immediately. The difference in speed is arguably negligible.

The second case is indeed a bit safer, as limiting the scope of your variables as much as possible is usually good practice.



回答6:

In JS the memory allocation is whole eachtime, In C#, there is no such difference usually, but if the local variable is captured by an anonymous method like lambda expression it will matter.



标签: c# .net scope