반응형
일반적으로 개발자는 블록을 벗어나면 지역변수가 가비지 콜렉터에 의해 정리될 것이라 생각하여 지역변수의 수명을 거의 신경쓰지 않는다.
하지만 클로저(Closure)는 이러한 규칙을 벗어난다.
캡처된 변수를 사용하는 마지막 델리게이트가 가비지화 될 때 까지 해당 변수는 가비지로 간주되지 않는다.
일반적으로 단순 메모리 리소스만 사용한다면 적절한 시점에 가비지로 수집될 것이기 때문에 신경쓸 필요 없지만
매우 무거운 리소스를 참고하고 있을 경우 더욱 신경써야 한다.
var counter = 0;
var numbers = Extensions.Generate(30, () => counter++);
이 코드는 실제로 다음과 같은 코드를 생성한다.
private class Closure
{
public int generatedCounter;
public int generatorFunc() =>
generatedCounter++;
}
// 사용 예
var c = new Closure();
c.generatedCounter = 0;
var sequence = Extensions.Generate(30, new Func<int>(c.generatorFunc));
내부적으로 중첩 클래스가 정의되며, 이 클래스는 Extension.Generate가 사용하는 델리게이트에 바인딩 된다.
델리게이트는 숨겨진 클래스 타입의 수명에 영향을 준다.
public IEnumerable<int> MakeSequence()
{
var counter = 0;
var numbers = Extensions.Generate(30, () => counter++);
return numbers;
}
위 코드 처럼 함수로 시퀀스를 반환하는 코드가 있다면,
델리게이트의 수명이 연장되고 델리게이트가 참조하는 내부 객체, 내부객체 안에 갭처된 객체도 도달 가능 상태가된다.
(도달 가능한 상태에서는 가비지가 되지 않는다.)
대부분의 경우에는 문제가 되지 않지만 IDisposable이 연관되어 있는경우 문제가 될 수 있다.
위의 코드를 사용하면 생성되는 코드는 다음과 같다.
public static IEnumerable<int> MakeSequence()
{
var c = new Closure();
c.generatedCounter = 0;
var sequence = Extensions.Generate(30, new Func<int>(c.generatorFunc));
return sequence;
}
결론
어떤 경우에서든 클로저에 의해 생성된 객체를 메서드가 반환하는 경우 클로저를 수행하기 위해 캡처됐던 모든 변수들이 그 안에 포함된다는 사실을 알아야한다.
반응형