É melhor declarar uma variável dentro ou fora de um loop?

É melhor fazer:

variable1Type foo; variable2Type baa; foreach(var val in list) { foo = new Foo( ... ); foo.x = FormatValue(val); baa = new Baa(); baa.main = foo; baa.Do(); } 

Ou:

 foreach(var val in list) { variable1Type foo = new Foo( ... ); foo.x = FormatValue(val); variable2Type baa = new Baa(); baa.main = foo; baa.Do(); } 

A questão é: o que é mais rápido 1 caso ou 2 caso? A diferença é insignificante? É o mesmo em aplicações reais? Isso pode ser uma otimização micro, mas eu realmente quero saber qual é o melhor.

Em termos de desempenho, vamos tentar exemplos concretos:

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

Eu deliberadamente escolhi tanto um tipo de valor quanto um tipo de referência, caso isso afete as coisas. Agora, o IL para eles:

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

Como você pode ver, além da ordem na pilha que o compilador escolheu – o que também poderia ter sido uma ordem diferente – não teve absolutamente nenhum efeito. Por sua vez, não há realmente nada que se esteja dando ao jitter para fazer muito uso do que o outro não está dando.

Fora isso, há uma espécie de diferença.

No meu Method1() , x sb são escopo para o foreach e não podem ser acessados ​​deliberadamente ou acidentalmente fora dele.

No meu Method2() , x sb não são conhecidos em tempo de compilation para ser atribuído de forma confiável um valor dentro do foreach (o compilador não sabe que o foreach executará pelo menos um loop), portanto, o uso dele é proibido.

Até agora, nenhuma diferença real.

No entanto, posso atribuir e usar x / ou sb fora do foreach . Como regra, eu diria que este é provavelmente um mau escopo na maioria das vezes, então eu preferiria o Method1 , mas eu poderia ter algum motivo sensato para querer me referir a eles (mais realisticamente se eles não fossem possivelmente não atribuídos), em qual caso eu iria para o Method2 .

Ainda assim, é uma questão de como cada código pode ser estendido ou não, e não uma diferença do código conforme está escrito. Realmente, não há diferença.

Não importa, não tem efeito no desempenho.

mas eu realmente quero saber fazer o caminho certo.

A maioria dirá que você faz o maior sentido.

É só uma questão de escopo. Nesse caso, onde foo e baa são usados ​​apenas dentro do loop for, é uma boa prática declará-los dentro do loop. É mais seguro também.

OK, eu respondi isso sem perceber onde o pôster original estava criando um novo object toda vez que passava pelo loop e não apenas ‘usando’ o object. Então, não, realmente não deve haver uma diferença insignificante quando se trata de desempenho. Com isso, eu iria com o segundo método e declararia o object dentro do loop. Dessa forma, ele será limpo durante a próxima passagem do GC e manterá o object dentro do escopo.

— Eu vou deixar a minha resposta original, só porque eu digitei tudo, e isso pode ajudar alguém que procura esta mensagem mais tarde. Prometo que, no futuro, prestarei mais atenção antes de tentar responder à próxima pergunta.

Tim

Na verdade, acho que há uma diferença. Se bem me lembro, toda vez que você criar um novo object = new foo() você terá esse object adicionado ao heap da memory. Então, criando os objects no loop, você estará adicionando a sobrecarga do sistema. Se você sabe que o loop vai ser pequeno, não é um problema.

Então, se você acabar em um loop com 1000 objects, você estará criando 1000 variables ​​que não serão descartadas até a próxima garbage collection. Agora bata em um database onde você quer fazer algo e você tem mais de 20.000 linhas para trabalhar … Você pode criar uma demanda bastante do sistema, dependendo do tipo de object que você está criando.

Isso deve ser fácil de testar … Crie um aplicativo que crie 10.000 itens com um registro de data e hora ao entrar no loop e ao sair. Na primeira vez que você fizer isso, declare a variável antes do loop e da próxima vez durante o loop. Você pode ter que aumentar essa contagem muito acima de 10K para ver uma diferença real na velocidade.

Além disso, há a questão do escopo. Se for criado no loop, ele será removido assim que você sair do loop, para que você não possa acessá-lo novamente. Mas isso também ajuda na limpeza, pois a garbage collection acabará descartando-a assim que sair do loop.

Tim

Ambos são completamente válidos, não tendo certeza de que haja um “caminho certo” para fazer isso.

Seu primeiro caso é mais eficiente em termos de memory (pelo menos a curto prazo). Declarar suas variables ​​dentro do loop forçará mais realocação de memory; no entanto, com o coletor de lixo .NET, à medida que essas variables ​​saem do escopo, elas serão limpas periodicamente, mas não necessariamente de imediato. A diferença de velocidade é indiscutivelmente desprezível.

O segundo caso é de fato um pouco mais seguro, pois limitar o escopo de suas variables ​​o máximo possível é geralmente uma boa prática.

Em JS, a alocação de memory é toda vez, Em C #, normalmente não existe essa diferença, mas se a variável local for capturada por um método anônimo como a expressão lambda, isso será importante.