ITEM 46 :: EFFECTIVE C#


안녕하세요, 46번째 시간입니다. using에 대해서는 이전에도 본 적이 있지만 참 잘 만들어놓은 기능입니다.


이번 챕터는 리소스 정리를 위해 using과 try/finally를 활용하라, 입니다. using은 c++에서 알고 있던 using의 기능 이상의 기능이 C#에 존재합니다. 그건 바로 using을 스코프와 함께 사용했을 때, 스코프가 끝나는 지점에서 리소스에 대한 정리를 해준다는 것 입니다. try/catch를 무분별하게 사용하는 것에 대해서 리소스 정리가 쉬워진 셈이죠.

설명

  1. 리소스 정리를 위해서는 Idisposable 인터페이스가 제공하고 있는 Dispose() 메서드를 이용하여 명시적으로 리소스를 해제해야 한다. 따라서, 어떠한 상황이든 사용하고 있는 메모리를 해제할 때 Dispose()가 호출되게 작성해야 한다. 이는 using을 사용하면 굉장히 좋다.

  2. using을 사용하면 C# 컴파일러가 그 블록에 try/finally를 자동으로 생성해주기 때문이다.

  3. 만약 Dispose가 호출되지 않을 경우를 대비하여 Finalizer를 방어적으로 작성해야 한다. 이럼에도 Finalize에 의해 우선순위가 낮아 나중에 정리되기 때문에 이는 성능저하와 비용으로 이어진다.

  4. 따라서 꼭 Dispose()가 호출되게 작성하라는 것이다.

  5. 다만 매번 using을 쓸 수 있는 것은 아니다. 예를들어, Idisposable 인터페이스를 지원하지 않는 타입에 대해서는 using문을 사용하면 컴파일 에러가 발생한다. 즉, 컴파일 타임에 Idisposable 인터페이스를 지원하는 타입에 대해서만 사용할 수 있다.

  6. 또, 컴파일 타임에 object로 해석되는 타입에 대해서도 Idisposable 인터페이스를 지원하지 않기 때문에 사용할 수 없다.

  7. 따라서, 이런 경우에는 using (obj as Idisposable) 과 같은 식으로 작성하면 문제가 없어진다.

  8. 위와같이 작성할 경우, 만약 Idisposable 인터페이스를 지원하는 경우 정상적으로 try/finally블록이 생성될 것이고, 그게 아니라면 using(null)로 인식하여 아무 작업도 하지않으므로 안전하다고 볼 수 있다.

  9. 그리고 생각해야될 점이 있는데, 하나의 메서드 내에서 두 개 이상의 Idisposable 인터페이스를 지원하는 객체를 사용할 경우, 각각에 대해 try/finally문이 생성된다. 이는 실행에 문제될 것은 아니지만, 최적화된 구성은 아니므로, try/finally 블록을 자체적으로 구현하는 것도 좋은 방법이다. (아니면 Using문을 중복해서 사용하든가!)

  10. 거기다, 이러한 객체들은 무조건 Using 블록이나 try 블록에서 생성되어야만 한다. 이전에 생성되는 경우에는 제대로 생성하기 이전에 예외가 발생하면 Dispose()가 호출되지 않기 때문이다. 그로인해 리소스 누수가 발생할 수 있으므로 주의를 해야한다.

  11. 또한 여러 타입중에 Close()와 Dispose() 두개가 구현되어있는 경우가 있는데, Close()를 사용하게 되면 GC.SuppressFinalize()를 호출하여 Finalizer를 호출할 필요가 없다고 GC에게 알리는 작업을 안하기 때문에, Finalizer큐에 그대로 남아있게 되어 성능에 좋지 않다. 꼭 불러야하는 경우가 아니라면 Dispose() 쪽을 선택하자.

  12. 또한 재사용 가능성이 있을경우 Dispose()를 절대 호출해선 안된다.

결론

결론은 자주 사용되는 대부분의 타입은 IDisposable을 구현하지 않으며, .NET Framework에 포함된 수 많은 타입 중에서 IDisposable을 구현하고 있는 타입은 일부에 지나지 않는다. 다만 IDisposable을 구현한 타입을 사용할 때는 반드시 Dispose() 메서드를 호출해야 한다는 것을 잊지 않기 바란다. 이 때 using문이나 try/finally 블록을 이용할 수 있으며 어떤 경우라도 올바르게 Dispose() 메서드가 호출될 수 있도록 코드를 작성해야 한다.