[41] 값비싼 리소스를 캡처하지 마라

다음 본문은 도서 이펙티브 C# (빌 와그너)에서 나오는 주제를 다룹니다.

 

 

개발자는 일반적으로 블록을 벗어나면 지역변수가 가비지 콜렉터에 의해 정리될 것이라 생각하여

지역변수의 수명을 거의 신경쓰지 않는다.

 

하지만 클로저(Closure)는 이러한 규칙을 벗어난다.

캡처된 변수를 사용하는 마지막 델리게이트가 가비지화될 때 까지 해당 변수는 가비지로 간주되지 않는다.

 

일반적으로 단순 메모리 리소스만 사용한다면 적절한 시점에 가비지로 수집될 것이기 때문에 신경쓸 필요없지만

매우 무거운 리소스를 참고하고 있을경우 더욱 신경써야 한다.

var counter = 0;
var numbers = Extensions.Generate(30, () => counter++);

이 코드는 실제로 다음과 같은 코드를 생성한다.

private class Closure
{
    public int generatedCounter;
    public int generatorFun() => 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;
}

생성되는 코드는 다음과같다. Closure 객체의 레퍼런스인 c는 지역변수임에도 델리게이트에 바인딩 되었기 때문에 메서드를 벗어나서도 여전히 살아남게된다.

public static IEnumerable<int> MakeSequence()
{
    var c = new Closure();
    c.generatedCounter = 0;
    var sequence = Extensions.Generate(30, new Func<int>(c.generatorFunc));
    
    return sequence;
}

 

결론

 어떤 경우에서든 클로저에 의해 생성된 객체를 메서드가 반환하는 경우 클로저를 수행하기 위해 캡처됐던 모든 변수들이 그 안에 포함된다는 사실을 알아야한다.

 

 

 

댓글

Designed by JB FACTORY