변수 캡처(Variable Capture) 는 람다 함수(익명 메서드) 또는 로컬 함수에서 바깥 스코프(외부)의 변수를 접근하고 유지하는 기능을 의미한다.
즉, 람다 표현식이나 익명 메서드가 자신이 선언된 스코프의 변수를 기억하고 사용할 수 있도록 한다.
using System;
class Program
{
static void Main()
{
int capturedVariable = 10; // 외부 변수
Action action = () =>
{
Console.WriteLine(capturedVariable); // 외부 변수 접근
};
capturedVariable = 20; // 외부 변수 변경
action(); // 20 출력 (변경된 값 반영)
}
}
action()이 실행될 때 capturedVariable의 값이 10이 아니라 20이 출력된다.C#의 변수 캡처 방식은 참조(Reference Capture) 방식과 값(Value Capture) 방식 두 가지로 나뉜다.
using System;
class Program
{
static void Main()
{
int counter = 0;
Action action = () => Console.WriteLine(counter);
counter = 10; // 변수 변경
action(); // 10 출력 (변경된 값 반영)
}
}
action()이 실행될 때 counter의 값이 10으로 변경된 상태이므로, 10이 출력된다.값 캡처 예제 (C# 5.0 이전)
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i)); // i 캡처
}
foreach (var action in actions)
{
action();
}
}
}
C# 5.0 이전의 출력
3
3
3
i == 3이 되어 모든 람다가 3을 출력한다.foreach 루프 변수도 참조 캡처 방식이 아니라, 값 캡처 방식이었다.using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<Action> actions = new List<Action>();
foreach (var i in new int[] { 0, 1, 2 })
{
actions.Add(() => Console.WriteLine(i)); // i 캡처
}
foreach (var action in actions)
{
action();
}
}
}
C# 5.0 이전의 출력
0
1
2
foreach의 변수 i는 값이 복사되어 각 람다가 별도의 변수를 캡처하기 때문에 예상대로 동작했다.foreach 변수 캡처 방식 변경foreach도 참조 캡처 방식으로 통일되었다.for와 foreach가 같은 방식으로 동작하게 되어 일관성이 유지되었다.using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<Action> actions = new List<Action>();
foreach (var i in new int[] { 0, 1, 2 })
{
int captured = i; // 명시적으로 복사하여 값 캡처
actions.Add(() => Console.WriteLine(captured));
}
foreach (var action in actions)
{
action();
}
}
}
출력 (C# 5.0 이후)
0
1
2
foreach도 for와 동일하게 참조 캡처를 사용한다.captured에 복사하면, 예상대로 동작하도록 강제할 수 있다.for 루프에서 변수 캡처 문제 해결 방법위에서 봤듯이, for 루프에서는 변수가 참조 캡처되기 때문에 의도하지 않은 동작이 발생할 수 있다.
이 문제를 해결하려면 반복문 내부에서 변수 복사(값 캡처) 를 해야 한다.
문제 발생 코드
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i)); // i는 참조 캡처됨
}
foreach (var action in actions)
{
action();
}
출력 (예상과 다르게 동작)
3
3
3
i가 참조 캡처되어, 반복문이 끝난 후 i == 3이 되기 때문이다.
해결 방법: 로컬 변수 사용 (값 캡처)
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
int captured = i; // 명시적으로 복사하여 값 캡처
actions.Add(() => Console.WriteLine(captured));
}
foreach (var action in actions)
{
action();
}
출력 (예상대로 동작)
0
1
2
| 개념 | 설명 |
|---|---|
| 변수 캡처 | 람다 함수 또는 익명 메서드가 외부 변수를 기억하고 사용하는 기능 |
| 참조 캡처 | 변수의 참조를 저장하여, 값이 변경되면 람다에서도 변경됨 (기본 동작) |
| 값 캡처 | 변수를 복사하여 람다에서 변경되지 않도록 유지 |
| for 루프 문제 해결 | 반복문 내부에서 int captured = i; 로 복사하여 해결 |
| C# 5.0 이후 변경 사항 | foreach도 for처럼 참조 캡처를 사용하도록 변경 |