ITEM 41 :: EFFECTIVE C#


안녕하세요, 41번째 시간입니다. 이제 9개밖에 안 남았어요!


이번 챕터는 값비싼 리소스를 캡처하지 말라, 입니다. 리소스에 관한 부분인 만큼 실제로 개발하는 부분에서 많은 부분을 차지하는 부분일거라 예상합니다. 자, 시작해볼까요?

설명

  1. 클로저라는 기능은, 클로저에 바인딩된 변수를 포함하는 객체를 생성한다. 클로저는 정확히 말하자면 함수 블록 내에서 함수 블록 바깥의 변수를 사용할 때 그 변수를 캡처된 클로저 변수라고 하고, 그 캡쳐된 클로저 변수를 사용하는 메서드 혹은 무명 람다식을 클로저라고 한다.

  2. 클로저와 캡쳐된 변수는 이러한 규칙을 벗어난다.

  3. 클로저내에서 변수를 캡처하면 이 변수가 참조하는 객체의 수명이 늘어나게 된다. 캡처된 변수를 사용하는 마지막 델리게이트가 가비지화될 때까지 해당 변수는 가비지로 간주되지 않는다.

  4. 이보다도 수명이 늘어날 수도 있다. 따라서, 캡처된 변수를 참조하는 클로저를 외부로 반환되면 캡처된 지역변수는 자신이 선언된 범위를 완전히 벗어나 생명주기가 지역변수와는 달라진다는 것이다. (포장해서 밖으로 가지고 나가는 상품이랑 비슷하다. 상품이 상점내에 있을 때엔 지역변수지만, 클로저(포장봉투)가 있다면 밖으로 나갈 수 있고, 그것의 생명주기는 그것을 가져간 주인의 손에 달려있다.)

  5. 다만 이러한 경우 가벼운 리소스를 참조하고 있을경우엔 큰 문제가 되지 않는다. 문제가 되는 부분은 매우 무거운 리소스를 참조하고 있을 경우이다.

  6. 이 경우 리소스 해제에 대한 정확한 이해가 없으면 리소스 혹은 메모리의 누수가 발생할 확률이 높다.

  7. 알고리즘 내에서 쿼리 표현식을 사용할 때 컴파일러는 단일 메서드 내의 모든 쿼리 표현식에 대해서 단 하나의 클로저만을 생성한다. 이렇게 내부적으로 생성된 객체는 메서드에 의해 반환될 수 있으며, 열거형 멤버를 포함할 수도 있다. 이 객체는 더 이상 사용되는 코드가 없을 때 삭제되는데, 이로 인해 문제가 발생할 수도 있다. 그것은 클로저 내에 IDisposable을 구현하고 있는 객체가 있는 경우, 그리고 무거운 객체가 포함되는 경우가 있으며, 이는 성능상의 문제를 유발할 가능성이 높다.

  8. 어느 경우든 클로저에 의해 생성된 객체를 메서드가 반환하는 경우 클로저를 수행하기 위해 캡쳐됐던 모든 변수들이 그 안에 포함된다는 사실을 알아야 한다. 따라서 반드시 그 변수가 필요한지를 면밀히 살펴야 하고, 클로저가 변수를 제대로 정리하는지를 확인해야 한다.

결론

결론적으로 봤을 때, 클로저라는 것은 일반적인 변수의 수명을 벗어날 수 밖에 없는 변수를 말한다. 원래라면 있을 수 없는 함수 블록 바깥에 있는 변수를 함수블록 내에서 사용한다는 행위를 가능하게 한 것이니만큼, 일반적인 변수의 규칙이 통용되지 않고, 또한, 생명 주기가 다르므로, 그 변수가 정리되기 전까지 메모리를 잡아먹는다는 이야기이다. 이것이 간단한 변수라면 그냥 넘어갈 수도 있지만, 이것이 무거운 리소스를 참조하고 있을 경우에는 그 메모리 낭비가 커져 성능 저하의 요인이 될 수도 있기 때문에 클로저를 사용할 경우, 변수가 정리되는 시점과 제대로 정리되는지에 대해 확실히 관리를 해야된다는 것이다!