TL; DR- 그것들은 IL 층에서 동등한 예입니다.
DotNetFiddle 은 결과 IL을 볼 수 있기 때문에 응답하기가 매우 쉽습니다 .
테스트 속도를 높이기 위해 약간 다른 루프 구조를 사용했습니다. 나는 사용했다 :
변형 1 :
using System;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
int x;
int i;
for(x=0; x<=2; x++)
{
i = x;
Console.WriteLine(i);
}
}
}
변형 2 :
Console.WriteLine("Hello World");
int x;
for(x=0; x<=2; x++)
{
int i = x;
Console.WriteLine(i);
}
두 경우 모두 컴파일 된 IL 출력이 동일하게 렌더링되었습니다.
.class public auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
.method public hidebysig static void Main() cil managed
{
//
.maxstack 2
.locals init (int32 V_0,
int32 V_1,
bool V_2)
IL_0000: nop
IL_0001: ldstr "Hello World"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ldc.i4.0
IL_000d: stloc.0
IL_000e: br.s IL_001f
IL_0010: nop
IL_0011: ldloc.0
IL_0012: stloc.1
IL_0013: ldloc.1
IL_0014: call void [mscorlib]System.Console::WriteLine(int32)
IL_0019: nop
IL_001a: nop
IL_001b: ldloc.0
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.0
IL_001f: ldloc.0
IL_0020: ldc.i4.2
IL_0021: cgt
IL_0023: ldc.i4.0
IL_0024: ceq
IL_0026: stloc.2
IL_0027: ldloc.2
IL_0028: brtrue.s IL_0010
IL_002a: ret
} // end of method Program::Main
따라서 귀하의 질문에 대답하기 위해 : 컴파일러는 변수 선언을 최적화하고 두 변형을 동일하게 렌더링합니다.
내 이해를 위해 .NET IL 컴파일러는 모든 변수 선언을 함수의 시작 부분으로 옮겼지만 분명히 2 라는 명확한 소스를 찾을 수 없었습니다 . 이 특정 예에서는 다음 명령문으로 이동했습니다.
.locals init (int32 V_0,
int32 V_1,
bool V_2)
우리는 비교할 때 너무 강박 적입니다 ....
사례 A, 모든 변수가 위로 올라 갑니까?
이것에 대해 조금 더 파기 위해 다음 기능을 테스트했습니다.
public static void Main()
{
Console.WriteLine("Hello World");
int x=5;
if (x % 2==0)
{
int i = x;
Console.WriteLine(i);
}
else
{
string j = x.ToString();
Console.WriteLine(j);
}
}
여기서 차이점 은 비교를 기반으로 int i
또는 하나를 선언한다는 것 string j
입니다. 다시, 컴파일러는 다음을 사용하여 모든 로컬 변수를 함수 2 의 맨 위로 이동합니다 .
.locals init (int32 V_0,
int32 V_1,
string V_2,
bool V_3)
int i
이 예제에서는 선언되지 않았지만 이를 지원하는 코드가 여전히 생성 된다는 점에 주목해야합니다 .
사례 B : foreach
대신에 for
?
그것은 지적 밖으로이었다 foreach
다른 행동을 가지고 for
내가 대해 질문했다 동일한 일을 확인되지 않았 음. 결과 IL을 비교하기 위해이 두 코드 섹션을 넣었습니다.
int
루프 외부의 선언 :
Console.WriteLine("Hello World");
List<int> things = new List<int>(){1, 2, 3, 4, 5};
int i;
foreach(var thing in things)
{
i = thing;
Console.WriteLine(i);
}
int
루프 내부의 선언 :
Console.WriteLine("Hello World");
List<int> things = new List<int>(){1, 2, 3, 4, 5};
foreach(var thing in things)
{
int i = thing;
Console.WriteLine(i);
}
foreach
루프가 있는 결과 IL은 루프를 사용하여 생성 된 IL과 실제로 다릅니다 for
. 특히, init 블록과 루프 섹션이 변경되었습니다.
.locals init (class [mscorlib]System.Collections.Generic.List`1<int32> V_0,
int32 V_1,
int32 V_2,
class [mscorlib]System.Collections.Generic.List`1<int32> V_3,
valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> V_4,
bool V_5)
...
.try
{
IL_0045: br.s IL_005a
IL_0047: ldloca.s V_4
IL_0049: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_004e: stloc.1
IL_004f: nop
IL_0050: ldloc.1
IL_0051: stloc.2
IL_0052: ldloc.2
IL_0053: call void [mscorlib]System.Console::WriteLine(int32)
IL_0058: nop
IL_0059: nop
IL_005a: ldloca.s V_4
IL_005c: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0061: stloc.s V_5
IL_0063: ldloc.s V_5
IL_0065: brtrue.s IL_0047
IL_0067: leave.s IL_0078
} // end .try
finally
{
IL_0069: ldloca.s V_4
IL_006b: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
IL_0071: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0076: nop
IL_0077: endfinally
} // end handler
이 foreach
접근법은 더 많은 지역 변수를 생성하고 추가 분기가 필요했습니다. 본질적으로 처음에는 루프의 끝으로 점프하여 열거의 첫 번째 반복을 얻은 다음 루프의 거의 맨 위로 이동하여 루프 코드를 실행합니다. 그런 다음 예상대로 계속 반복됩니다.
그러나 for
및 foreach
구문 을 사용하여 발생하는 분기 차이를 넘어선 선언 위치에 따라 IL 에는 차이 가 없었int i
습니다. 그래서 우리는 여전히 두 가지 접근법이 동등합니다.
사례 C : 다른 컴파일러 버전은 어떻습니까?
1 로 남겨둔 의견 에는 foreach를 사용한 변수 액세스 및 클로저 사용에 관한 경고와 관련된 SO 질문에 대한 링크가있었습니다 . 그 질문에 정말로 주목 한 부분은 .NET 4.5 컴파일러와 이전 버전의 컴파일러의 작동 방식에 차이가있을 수 있다는 것입니다.
그리고 그것이 바로 DotNetFiddler 사이트에서 알려 드린 곳입니다. .NET 4.5와 Roslyn 컴파일러 버전 만 있으면됩니다. 그래서 Visual Studio의 로컬 인스턴스를 가져 와서 코드 테스트를 시작했습니다. 동일한 내용을 비교하기 위해 .NET 4.5에서 로컬로 작성된 코드를 DotNetFiddler 코드와 비교했습니다.
내가 주목 한 유일한 차이점은 로컬 초기화 블록과 변수 선언과 관련이 있습니다. 로컬 컴파일러는 변수 이름을 지정하는 데 조금 더 구체적이었습니다.
.locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> things,
[1] int32 thing,
[2] int32 i,
[3] class [mscorlib]System.Collections.Generic.List`1<int32> '<>g__initLocal0',
[4] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> CS$5$0000,
[5] bool CS$4$0001)
그러나 그 작은 차이로, 지금까지는 아주 좋았습니다. DotNetFiddler 컴파일러와 로컬 VS 인스턴스가 생성 한 것 사이에 동등한 IL 출력이있었습니다.
그런 다음 .NET 4, .NET 3.5 및 .NET 3.5 릴리스 모드를 대상으로하는 프로젝트를 다시 작성했습니다.
그리고이 세 가지 경우 모두에서 생성 된 IL은 동일합니다. 대상 .NET 버전은 이러한 샘플에서 생성 된 IL에 영향을 미치지 않았습니다.
이 모험을 요약하면 : 나는 컴파일러가 기본 유형을 선언하는 위치를 신경 쓰지 않으며 선언 방법을 사용하여 메모리 또는 성능에 영향을 미치지 않는다고 자신있게 말할 수 있다고 생각합니다. 그리고 for
or foreach
루프 를 사용하더라도 관계없이 적용 됩니다.
foreach
루프 내부에 클로저를 통합 한 또 다른 사례를 실행하는 것을 고려했습니다 . 그러나 기본 유형 변수가 선언 된 위치의 영향에 대해 질문 했으므로 관심있는 것보다 너무 많이 탐구하고 있다고 생각했습니다. 앞에서 언급 한 SO 질문에는 foreach 반복 변수에 대한 클로저 효과에 대한 좋은 개요를 제공하는 훌륭한 답변 이 있습니다.
1 루프 내에서 SO 질문 해결 클로저에 대한 원래 링크를 제공 한 Andy에게 감사합니다 foreach
.
2 ECMA-335 사양 은 I.12.3.2.2 '로컬 변수 및 인수'섹션에서이를 해결 한다는 점에 주목할 가치가 있습니다. 결과 IL을 확인한 다음 진행 상황에 대해 명확하게하기 위해 해당 섹션을 읽어야했습니다. 채팅에서 지적 해준 래칫 괴물에게 감사합니다.