C#에서 람다를 사용하여 람다 밖에서 사용 중인 변수를 가져오면(캡처하면) 항상 레퍼런스로 전달되는 것 처럼 처리가 된다. (람다-클로저 관련 원리에 대한 간략한 포스팅)
for (int i = 0; i < 10; ++i)
{
new Thread(() =>
{
Thread.Sleep(1000);
Console.WriteLine(i);
}).Start();
}
// 10이 10번 출력됨.
위의 경우에는 하나의 i가 10번 레퍼런스 전달되었고, 1초뒤에 forloop가 전부 끝나서 10이 된 i가 10번 출력되게 된다.
그렇다면 foreach를 사용하면 어떨까?
foreach (int i in Enumerable.Range(1, 10))
{
new Thread(() =>
{
Thread.Sleep(1000);
Console.WriteLine(i);
}).Start();
}
// 1~10 까지 잘 출력됨.
foreach는 놀랍게도 원하는 대로 출력된다.
반복자인 int i가 하나의 원본이 아닌 것 처럼 보이는데 어떻게 된 일일까?
// C# 5.0 이전 -------------------------------------------
var it = Enumerable.Range(1, 10).GetEnumerator();
int i;
while (it.MoveNext())
{
i = it.Current; // 하나의 주소가 계속 캡쳐되어 값이 불안정함.
new Thread(() =>
{
Thread.Sleep(1000);
Console.WriteLine(i);
}).Start();
}
// 10이 10번 출력됨.
// C# 5.0 이후 -------------------------------------------
var it = Enumerable.Range(1, 10).GetEnumerator();
while (it.MoveNext())
{
int i = it.Current; // 항상 새로운 값으로 캡쳐되어 반복자가 닫힘.
new Thread(() =>
{
Thread.Sleep(1000);
Console.WriteLine(i);
}).Start();
}
// 1~10 까지 잘 출력됨.
알고 보니 C# 5.0부터 foreach 문은 이런 문제가 발생하지 않도록 하기 위해 위와 같이 항상 반복자를 새로 캡처하여 넘기는 것으로 변경되었다. 이른바 [루프 변수 닫기]라고 한다.
덕분에 Async 로직이나 지연 평가를 하는 부분에서 복잡한 못한 문제가 발생하지 않고 있었다. 갓샾.
관련 자료.
Closing over the loop variable considered harmful
Table of contents Closing over the loop variable considered harmful Article 11/12/2009 In this article --> (This is part one of a two-part series on the loop-variable-closure problem. Part two is here.) UPDATE: We are taking the breaking change. In C# 5, t
learn.microsoft.com
https://learn.microsoft.com/ko-kr/archive/blogs/ericlippert/closing-over-the-loop-variable-part-two
'Programming > C# & Unity' 카테고리의 다른 글
C# perfmon 샘플 코드 (0) | 2023.08.26 |
---|---|
C# 람다 클로저 원리 (0) | 2023.08.26 |
C# 박싱 (2) | 2023.04.29 |
C# static 멤버 함수 (0) | 2022.11.20 |
C# Async Await 원리 (2) | 2022.11.16 |