Y a-t-il une différence entre ces deux versions de code?
foreach (var thing in things)
{
int i = thing.number;
// code using 'i'
// pay no attention to the uselessness of 'i'
}
int i;
foreach (var thing in things)
{
i = thing.number;
// code using 'i'
}
Ou le compilateur s'en fiche-t-il? Quand je parle de différence, je veux dire en termes de performances et d'utilisation de la mémoire. .. Ou fondamentalement n'importe quelle différence ou les deux finissent-ils par être le même code après la compilation?
c#
performance
memory
Alternatex
la source
la source
Réponses:
TL; DR - ce sont des exemples équivalents au niveau de la couche IL.
DotNetFiddle rend cette jolie réponse car elle vous permet de voir l'IL résultant.
J'ai utilisé une variation légèrement différente de votre construction de boucle afin de rendre mes tests plus rapides. J'ai utilisé:
Variation 1:
Variation 2:
Dans les deux cas, la sortie IL compilée est restée la même.
Donc pour répondre à votre question: le compilateur optimise la déclaration de la variable, et rend les deux variantes équivalentes.
À ma connaissance, le compilateur .NET IL déplace toutes les déclarations de variables au début de la fonction, mais je n'ai pas pu trouver une bonne source qui indique clairement que 2 . Dans cet exemple particulier, vous voyez qu'il les a déplacés vers le haut avec cette instruction:
Où nous devenons un peu trop obsessionnels pour faire des comparaisons ...
Cas A, toutes les variables sont-elles déplacées vers le haut?
Pour creuser un peu plus, j'ai testé la fonction suivante:
La différence ici est que nous déclarons un
int i
ou unstring j
basé sur la comparaison. Encore une fois, le compilateur déplace toutes les variables locales en haut de la fonction 2 avec:J'ai trouvé intéressant de noter que même s'il
int i
ne sera pas déclaré dans cet exemple, le code pour le prendre en charge est toujours généré.Cas B: Et au
foreach
lieu defor
?Il a été souligné que le
foreach
comportement est différent defor
celui et que je ne vérifiais pas la même chose qui avait été posée. J'ai donc mis ces deux sections de code pour comparer l'IL résultant.int
déclaration en dehors de la boucle:int
déclaration à l'intérieur de la boucle:L'IL résultant avec la
foreach
boucle était en effet différent de l'IL généré en utilisant lafor
boucle. Plus précisément, le bloc init et la section de boucle ont changé.L'
foreach
approche a généré plus de variables locales et a nécessité des branchements supplémentaires. Essentiellement, la première fois, il saute à la fin de la boucle pour obtenir la première itération de l'énumération, puis revient presque au sommet de la boucle pour exécuter le code de la boucle. Il continue ensuite à boucler comme prévu.Mais au-delà des différences de branchement causées par l'utilisation des constructions
for
etforeach
, il n'y avait aucune différence dans l'IL en fonction de l'emplacement de laint i
déclaration. Nous sommes donc toujours aux deux approches étant équivalentes.Cas C: Qu'en est-il des différentes versions du compilateur?
Dans un commentaire qui a été laissé 1 , il y avait un lien vers une question SO concernant un avertissement concernant l'accès variable avec foreach et l'utilisation de la fermeture . La partie qui a vraiment attiré mon attention dans cette question était qu'il y avait peut-être des différences dans le fonctionnement du compilateur .NET 4.5 par rapport aux versions antérieures du compilateur.
Et c'est là que le site DotNetFiddler m'a laissé tomber - tout ce qu'ils avaient à disposition était .NET 4.5 et une version du compilateur Roslyn. J'ai donc mis en place une instance locale de Visual Studio et commencé à tester le code. Pour m'assurer que je comparais les mêmes choses, j'ai comparé le code construit localement à .NET 4.5 au code DotNetFiddler.
La seule différence que j'ai notée était avec le bloc init local et la déclaration de variable. Le compilateur local était un peu plus spécifique pour nommer les variables.
Mais avec cette petite différence, c'était si loin, tellement bon. J'avais une sortie IL équivalente entre le compilateur DotNetFiddler et ce que mon instance VS locale produisait.
J'ai donc reconstruit le projet en ciblant .NET 4, .NET 3.5, et pour faire bonne mesure .NET 3.5 Release mode.
Et dans ces trois cas supplémentaires, l'IL généré était équivalent. La version .NET ciblée n'a eu aucun effet sur l'IL généré dans ces échantillons.
Pour résumer cette aventure: je pense que nous pouvons dire avec confiance que le compilateur ne se soucie pas de l'endroit où vous déclarez le type primitif et qu'il n'y a aucun effet sur la mémoire ou les performances avec l'une ou l'autre méthode de déclaration. Et cela reste vrai indépendamment de l'utilisation d'une boucle
for
ouforeach
.J'ai envisagé de lancer un autre cas qui incorporait une fermeture à l'intérieur de la
foreach
boucle. Mais vous aviez posé des questions sur les effets de la déclaration d'une variable de type primitif, alors j'ai pensé que j'allais trop loin au-delà de ce que vous vouliez savoir. La question SO que j'ai mentionnée plus tôt a une excellente réponse qui fournit un bon aperçu des effets de fermeture sur les variables d'itération foreach.1 Merci à Andy d'avoir fourni le lien d'origine vers la question SO traitant des fermetures dans les
foreach
boucles.2 Il convient de noter que la spécification ECMA-335 traite de cela avec la section I.12.3.2.2 «Variables et arguments locaux». J'ai dû voir l'IL obtenu et lire la section pour qu'il soit clair sur ce qui se passait. Merci à Ratchet Freak d'avoir signalé cela dans le chat.
la source
foreach
boucle et j'ai également vérifié la version .NET ciblée.Selon le compilateur que vous utilisez (je ne sais même pas si C # en a plus d'un), votre code sera optimisé avant d'être transformé en programme. Un bon compilateur verra que vous réinitialisez la même variable à chaque fois avec une valeur différente et gérez efficacement l'espace mémoire pour cela.
Si vous initialisiez la même variable à une constante à chaque fois, le compilateur l'initialisait également avant la boucle et la référençait.
Tout dépend de la qualité de l'écriture de votre compilateur, mais en ce qui concerne les normes de codage, les variables doivent toujours avoir la portée la plus faible possible . Donc, déclarer à l'intérieur de la boucle est ce que j'ai toujours appris.
la source
au début, vous déclarez et initialisez simplement la boucle intérieure de sorte que chaque boucle de temps sera réinitialisée à l'intérieur de la boucle "i". En second lieu, vous déclarez uniquement en dehors de la boucle.
la source