ITEM 46 :: EFFECTIVE C#
안녕하세요, 46번째 시간입니다. using에 대해서는 이전에도 본 적이 있지만 참 잘 만들어놓은 기능입니다.
이번 챕터는 리소스 정리를 위해 using과 try/finally를 활용하라, 입니다. using은 c++에서 알고 있던 using의 기능 이상의 기능이 C#에 존재합니다. 그건 바로 using을 스코프와 함께 사용했을 때, 스코프가 끝나는 지점에서 리소스에 대한 정리를 해준다는 것 입니다. try/catch를 무분별하게 사용하는 것에 대해서 리소스 정리가 쉬워진 셈이죠.
설명
-
리소스 정리를 위해서는 Idisposable 인터페이스가 제공하고 있는 Dispose() 메서드를 이용하여 명시적으로 리소스를 해제해야 한다. 따라서, 어떠한 상황이든 사용하고 있는 메모리를 해제할 때 Dispose()가 호출되게 작성해야 한다. 이는 using을 사용하면 굉장히 좋다.
-
using을 사용하면 C# 컴파일러가 그 블록에 try/finally를 자동으로 생성해주기 때문이다.
-
만약 Dispose가 호출되지 않을 경우를 대비하여 Finalizer를 방어적으로 작성해야 한다. 이럼에도 Finalize에 의해 우선순위가 낮아 나중에 정리되기 때문에 이는 성능저하와 비용으로 이어진다.
-
따라서 꼭 Dispose()가 호출되게 작성하라는 것이다.
-
다만 매번 using을 쓸 수 있는 것은 아니다. 예를들어, Idisposable 인터페이스를 지원하지 않는 타입에 대해서는 using문을 사용하면 컴파일 에러가 발생한다. 즉, 컴파일 타임에 Idisposable 인터페이스를 지원하는 타입에 대해서만 사용할 수 있다.
-
또, 컴파일 타임에 object로 해석되는 타입에 대해서도 Idisposable 인터페이스를 지원하지 않기 때문에 사용할 수 없다.
-
따라서, 이런 경우에는 using (obj as Idisposable) 과 같은 식으로 작성하면 문제가 없어진다.
-
위와같이 작성할 경우, 만약 Idisposable 인터페이스를 지원하는 경우 정상적으로 try/finally블록이 생성될 것이고, 그게 아니라면 using(null)로 인식하여 아무 작업도 하지않으므로 안전하다고 볼 수 있다.
-
그리고 생각해야될 점이 있는데, 하나의 메서드 내에서 두 개 이상의 Idisposable 인터페이스를 지원하는 객체를 사용할 경우, 각각에 대해 try/finally문이 생성된다. 이는 실행에 문제될 것은 아니지만, 최적화된 구성은 아니므로, try/finally 블록을 자체적으로 구현하는 것도 좋은 방법이다. (아니면 Using문을 중복해서 사용하든가!)
-
거기다, 이러한 객체들은 무조건 Using 블록이나 try 블록에서 생성되어야만 한다. 이전에 생성되는 경우에는 제대로 생성하기 이전에 예외가 발생하면 Dispose()가 호출되지 않기 때문이다. 그로인해 리소스 누수가 발생할 수 있으므로 주의를 해야한다.
-
또한 여러 타입중에 Close()와 Dispose() 두개가 구현되어있는 경우가 있는데, Close()를 사용하게 되면 GC.SuppressFinalize()를 호출하여 Finalizer를 호출할 필요가 없다고 GC에게 알리는 작업을 안하기 때문에, Finalizer큐에 그대로 남아있게 되어 성능에 좋지 않다. 꼭 불러야하는 경우가 아니라면 Dispose() 쪽을 선택하자.
-
또한 재사용 가능성이 있을경우 Dispose()를 절대 호출해선 안된다.
결론
결론은 자주 사용되는 대부분의 타입은 IDisposable을 구현하지 않으며, .NET Framework에 포함된 수 많은 타입 중에서 IDisposable을 구현하고 있는 타입은 일부에 지나지 않는다. 다만 IDisposable을 구현한 타입을 사용할 때는 반드시 Dispose() 메서드를 호출해야 한다는 것을 잊지 않기 바란다. 이 때 using문이나 try/finally 블록을 이용할 수 있으며 어떤 경우라도 올바르게 Dispose() 메서드가 호출될 수 있도록 코드를 작성해야 한다.