Existe uma diferença entre essas duas versões do código?
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 o compilador não se importa? Quando falo da diferença, quero dizer em termos de desempenho e uso de memória. ..Ou basicamente qualquer diferença ou os dois acabam sendo o mesmo código após a compilação?
c#
performance
memory
Alternatex
fonte
fonte
Respostas:
TL; DR - são exemplos equivalentes na camada IL.
O DotNetFiddle torna isso bonito de responder, pois permite ver a IL resultante.
Usei uma variação ligeiramente diferente da sua construção de loop para tornar meus testes mais rápidos. Eu usei:
Variação 1:
Variação 2:
Nos dois casos, a saída IL compilada foi renderizada da mesma forma.
Então, para responder à sua pergunta: o compilador otimiza a declaração da variável e torna as duas variações equivalentes.
Para meu entendimento, o compilador .NET IL move todas as declarações de variáveis para o início da função, mas não consegui encontrar uma boa fonte que indicasse claramente 2 . Neste exemplo em particular, você vê que os moveu com esta declaração:
Onde ficamos um pouco obsessivos em fazer comparações ...
Caso A, todas as variáveis são movidas para cima?
Para aprofundar um pouco mais, testei a seguinte função:
A diferença aqui é que declaramos um
int i
ou umstring j
com base na comparação. Novamente, o compilador move todas as variáveis locais para o topo da função 2 com:Achei interessante notar que, embora
int i
não seja declarado neste exemplo, o código para suportá-lo ainda é gerado.Caso B: E em
foreach
vez defor
?Foi apontado que ele
foreach
tem um comportamento diferentefor
e que eu não estava verificando a mesma coisa que havia sido perguntada. Então, eu coloquei nessas duas seções de código para comparar a IL resultante.int
declaração fora do loop:int
declaração dentro do loop:A IL resultante com o
foreach
loop era de fato diferente da IL gerada usando ofor
loop. Especificamente, o bloco init e a seção do loop foram alterados.A
foreach
abordagem gerou mais variáveis locais e exigiu algumas ramificações adicionais. Essencialmente, na primeira vez, ele pula para o final do loop para obter a primeira iteração da enumeração e depois volta para quase a parte superior do loop para executar o código do loop. Em seguida, continua a percorrer conforme o esperado.Porém, além das diferenças de ramificação causadas pelo uso das construções
for
eforeach
, não houve diferença na IL com base no local em que aint i
declaração foi colocada. Portanto, ainda estamos nas duas abordagens sendo equivalentes.Caso C: E as diferentes versões do compilador?
Em um comentário deixado 1 , havia um link para uma pergunta de SO referente a um aviso sobre acesso variável ao foreach e uso de fechamento . A parte que realmente chamou minha atenção nessa pergunta foi que pode ter havido diferenças em como o compilador .NET 4.5 funcionava em relação às versões anteriores do compilador.
E foi aí que o site DotNetFiddler me decepcionou - tudo o que eles tinham disponível era o .NET 4.5 e uma versão do compilador Roslyn. Então, criei uma instância local do Visual Studio e comecei a testar o código. Para garantir que eu comparasse as mesmas coisas, comparei o código criado localmente no .NET 4.5 com o código DotNetFiddler.
A única diferença que notei foi com o bloco init local e a declaração da variável. O compilador local foi um pouco mais específico ao nomear as variáveis.
Mas com essa pequena diferença, foi tão longe, tão bom. Eu tinha saída IL equivalente entre o compilador DotNetFiddler e o que minha instância local do VS estava produzindo.
Então, reconstruí o projeto direcionado ao .NET 4, .NET 3.5 e, em boa medida, ao modo .NET 3.5 Release.
E nos três casos adicionais, a IL gerada era equivalente. A versão do .NET direcionada não teve efeito na IL gerada nessas amostras.
Para resumir esta aventura: Acho que podemos dizer com segurança que o compilador não se importa com o local onde você declara o tipo primitivo e que não há efeito na memória ou no desempenho com qualquer método de declaração. E isso é válido independentemente do uso de um
for
ouforeach
loop.Eu considerei executar outro caso que incorporava um fechamento dentro do
foreach
loop. Mas você perguntou sobre os efeitos de onde uma variável do tipo primitiva foi declarada, então imaginei que estava indo muito além do que você estava interessado em perguntar. A pergunta SO mencionada anteriormente tem uma ótima resposta que fornece uma boa visão geral sobre os efeitos de fechamento nas variáveis de iteração foreach.1 Obrigado a Andy por fornecer o link original para a pergunta SO, abordando fechamentos em
foreach
loops.2 Vale ressaltar que a especificação do ECMA-335 trata disso na seção I.12.3.2.2 'Variáveis e argumentos locais'. Eu tive que ver a IL resultante e depois ler a seção para ficar claro sobre o que estava acontecendo. Obrigado à catraca por apontar isso no chat.
fonte
foreach
loop e também verifiquei a versão .NET direcionada.Dependendo do compilador que você usa (nem sei se o C # tem mais de um), seu código será otimizado antes de ser transformado em um programa. Um bom compilador verá que você está reinicializando a mesma variável a cada vez com um valor diferente e gerenciando o espaço de memória para ela com eficiência.
Se você estivesse inicializando a mesma variável em uma constante cada vez, o compilador também a inicializaria antes do loop e a referenciaria.
Tudo depende de quão bem o seu compilador é escrito, mas no que diz respeito aos padrões de codificação, as variáveis devem sempre ter o menor escopo possível . Então, declarar dentro do loop é o que sempre fui ensinado.
fonte
em primeiro lugar, você está apenas declarando e inicializando o loop interno para que, sempre que o loop for repetido, ele será reinicializado "i" no loop interno. No segundo, você está declarando apenas fora do loop.
fonte