Est-il préférable de déclarer une variable à l’intérieur ou à l’extérieur d’une boucle?

C’est mieux faire:

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

La question est: Qu’est-ce qui est plus rapide 1 cas ou 2 cas? Est-ce que la différence est insignifiante? Est-ce la même chose dans les applications réelles? C’est peut-être une optimisation micro, mais je veux vraiment savoir lequel est le meilleur.

Sur le plan des performances, essayons des exemples concrets:

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

J’ai délibérément choisi un type de valeur et un type de référence au cas où cela aurait une incidence sur les choses. Maintenant, le IL pour eux:

 .method public hidebysig instance void Method1() cil managed { .maxstack 2 .locals init ( [0] int32 i, [1] int32 x, [2] class [mscorlib]System.Text.SsortingngBuilder 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.SsortingngBuilder::.ctor() L_0020: stloc.2 L_0021: ldloc.2 L_0022: ldloc.1 L_0023: callvirt instance class [mscorlib]System.Text.SsortingngBuilder [mscorlib]System.Text.SsortingngBuilder::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.SsortingngBuilder 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.SsortingngBuilder::.ctor() L_0020: stloc.1 L_0021: ldloc.1 L_0022: ldloc.0 L_0023: callvirt instance class [mscorlib]System.Text.SsortingngBuilder [mscorlib]System.Text.SsortingngBuilder::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 } 

Comme vous pouvez le constater, à part l’ordre dans la stack que le compilateur a choisi – qui aurait tout aussi bien pu être un ordre différent – cela n’a eu absolument aucun effet. À son tour, il n’y a pas vraiment quoi que ce soit que l’on donne à la gigue pour utiliser beaucoup que l’autre ne le donne pas.

Autre que cela, il y a une sorte de différence.

Dans ma Method1() , x et sb sont liés à foreach et ne sont accessibles ni délibérément ni accidentellement en dehors de celle-ci.

Dans ma Method2() , x et sb ne sont pas connus à la compilation pour se voir atsortingbuer de manière fiable une valeur dans le foreach (le compilateur ignore que le foreach effectuera au moins une boucle), son utilisation est donc interdite.

Jusqu’à présent, pas de réelle différence.

Je peux cependant assigner et utiliser x et / ou sb dehors de foreach . En règle générale, je dirais que la plupart du temps, la scope est probablement médiocre. Je serais donc en faveur de Method1 , mais j’aurais peut-être une raison raisonnable de vouloir y faire référence (de manière plus réaliste, si elles n’étaient pas éventuellement non atsortingbuées), auquel cas j’irais pour Method2 .

Reste que c’est une question de savoir comment chaque code peut être étendu ou non, pas une différence de code écrit. Vraiment, il n’y a pas de différence.

Peu importe, cela n’a aucun effet sur les performances.

mais je veux vraiment savoir faire le bon chemin.

La plupart vous diront que dans la boucle, c’est ce qui est le plus logique.

C’est juste une question de scope. Dans ce cas, où foo et baa ne sont utilisés que dans la boucle for, il est recommandé de les déclarer dans la boucle. C’est plus sûr aussi.

D’accord, j’ai répondu à cette question sans remarquer où l’affiche originale créait un nouvel object à chaque fois en passant par la boucle et non pas simplement en utilisant l’object. Donc, non, il ne devrait pas y avoir vraiment de différence négligeable en matière de performance. Avec cela, je voudrais aller avec la deuxième méthode et déclarer l’object dans la boucle. De cette façon, il sera nettoyé lors de la prochaine passe de GC et le maintiendra dans la scope.

— Je laisserai ma réponse originale juste parce que je l’ai typescripte et que cela pourrait aider quelqu’un d’autre qui cherche ce message plus tard. Je promets qu’à l’avenir, je ferai plus attention avant d’essayer de répondre à la question suivante.

Tim

En fait, je pense qu’il y a une différence. Si je me souviens bien, chaque fois que vous créez un nouvel object = new foo() , cet object sera ajouté à la mémoire. Donc, en créant les objects dans la boucle, vous allez append à la surcharge du système. Si vous savez que la boucle sera petite, ce n’est pas un problème.

Donc, si vous vous retrouvez dans une boucle avec 1 000 objects, vous allez créer 1 000 variables qui ne seront pas éliminées avant la prochaine collecte de place. Appuyez maintenant sur une firebase database sur laquelle vous voulez faire quelque chose et avec laquelle vous avez plus de 20 000 lignes à travailler … Vous pouvez créer une demande système assez importante en fonction du type d’object que vous créez.

Cela devrait être facile à tester … Créez une application qui crée 10 000 articles avec un horodatage lorsque vous entrez dans la boucle et lorsque vous quittez. La première fois que vous le faites, déclarez la variable avant la boucle et la prochaine fois pendant la boucle. Cependant, vous devrez peut-être dépasser ce nombre beaucoup plus élevé que 10K pour voir une réelle différence de vitesse.

De plus, il y a le problème de la scope. S’il est créé dans la boucle, il disparaît dès que vous quittez la boucle, vous ne pouvez donc plus y accéder. Mais cela facilite également le nettoyage car la collecte des ordures finira par en disposer une fois sortie de la boucle.

Tim

Les deux sont tout à fait valables, il n’est pas sûr qu’il existe une «bonne façon» de procéder.

Votre premier cas est plus efficace en termes de mémoire (du moins à court terme). Déclarer vos variables à l’intérieur de la boucle obligera à plus de réallocation de mémoire; Cependant, avec .NETs garbage collector, ces variables étant hors de scope, elles seront nettoyées périodiquement, mais pas nécessairement immédiatement. La différence de vitesse est sans doute négligeable.

Le second cas est en effet un peu plus sûr, car limiter le plus possible la scope de vos variables est généralement une bonne pratique.

Dans JS, l’allocation de mémoire est entière à chaque fois. En C #, il n’y a généralement pas de différence de ce type, mais si la variable locale est capturée par une méthode anonyme telle que l’expression lambda, elle importera.