C# .NET IDisposable을 이용한 Dispose pattern과 SafeHandle 기술 이야기 2020. 5. 26. 23:41

C# .NET에서는 효율적이 메모리 관리를 위해 Dispose pattern과 SafeHandle의 사용이 권장된다. 짧게 요약해 보았다.

For the majority of the objects that your app creates, you can rely on .NET's garbage collector to handle memory management. However, when you create objects that include unmanaged resources, you must explicitly release those resources when you finish using them in your app.

Cleaning Up Unmanaged Resources

패턴을 잘 활용하는 것은 여러 장점을 갖는다

대부분의 경우, 쓰레기 수집상에 맡기면 문제가 없지만 그렇지 못한 경우도 있다. Unmanaged resource 라고 표현을 하는데, 말 그대로 시스템이 관리해주지 않는, 직접 관리해야 하는 리소스를 의미한다. 파일이나 윈도우즈, 네트워크 연결등이 거론되는데, 임의로 할 당한 힙 메모리도 포함된다. malloc 같은.

Dispose pattern

Dispose 패턴은 IDisposable 인터페이스를 이용하지만, 2개의 메소드로 구현하여 효과적으로 자원을 해제하는 패턴이다. 일반적이며, 정형화된 구현 방법이기 때문에 패턴이라고 한다.

The IDisposable interface requires the implementation of a single parameterless method, Dispose. However, the dispose pattern requires two Dispose methods to be implemented:

Implementing-dispose

간단하게 설명하면, void Dispose()void Dispose(bool disposing)로 구성되는데, void Dispose()로 종료될 수도 있고, Object.Finalize로 종료 될 수 있다고 보면 된다. 둘 중 어떤 것으로 종료되더라도 unmanaged resource는 직접 해제 되도록 구성되며, managed resourece의 경우는 명시적으로 void Dispose()가 호출될 때 해제되는 방식이다. 즉, unmanaged resource가 해제되는 것은 변함이 없으나, 명시적으로 managed resource를 해제함으로 보다 효율적으로 메모리를 관리하는 것이 Dispose pattern의 핵심이다.

아래는 Object.Finalize를 별도로 구성하지 않고, SafeHandle을 사용하는 경우의 샘플 코드.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class BaseClass : IDisposable
{
   // Flag: Has Dispose already been called?
   bool disposed = false;
   // Instantiate a SafeHandle instance.
   SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);

   // Public implementation of Dispose pattern callable by consumers.
   public void Dispose()
   {
      Dispose(true);
      // Object.Finalize가 따로 구현되지 않은 경우, 아래 코드는 아무런 영향이 없다.
      GC.SuppressFinalize(this);
   }

   // Protected implementation of Dispose pattern.
   protected virtual void Dispose(bool disposing)
   {
      if (disposed)
         return;

      if (disposing) {
         handle.Dispose();
         // Free any other managed objects here.
      }

      disposed = true;
   }
}

void Dispose() 로 종료되는 경우

이 경우는 Dispose() 메소드가 직접 호출된 경우이다. Dispose(true)를 호출해서 managed resourceunmanaged resource를 모두 해제하고, GC.SuppressFinalize(this)를 통해서 Object.Finalize가 호출되지 않도록 하며, 자원 해제가 종료된다.

Object.Finalize 로 종료되는 경우

이 경우는 Dispose() 메소드의 호출 없이 GC가 Object.Finalize를 호출한 경우다. 소멸자 안에서 Dispose(false)를 호출해서 unmanaged resource만 해제하고 종료된다. managed resource의 경우는 GC에 의해서 자원 해제가 되었거나 될 것으로 기대해도 좋기 때문이다.

void Dispose(bool disposing)

disposing은 managed resource를 해제할 것인지 여부를 결정하는 것이다. 명시적으로 Dispose()에서 호출되었는지, Object.Finalize에서 호출되었는지의 차이가 되며, 위에서 언급한 대로 managed resource를 명시적으로 해제함으로써 보다 효율적으로 메모리를 관리할 수 있게 된다.

SafeHandle

Operating System handle의 안전한 사용을 위한 랩퍼 클래스이다. unmanaged resource의 안전한 메모리 관리를 위해서 사용이 권장된다. 이것이 나오기 전에는, 소멸자에서 예외 상황이 발생한다거나 이미 소멸된 자원에 다시 접근 한다거나, 중복 해제 시도등의 문제가 왕왕있었나 보다. 그러한 문제들을 해결하기 위해서 만들어 주신것이니 아낌없이 사용해주면 되겠다!

SafeHanlde을 사용하면, managed resource로 감싸지기 때문에 메모리 관리도 한결 수월해 진다. Dispose pattern과 함께 사용이 권장되며, 위의 예제 코드에서와 같이 if(disposing) 안에서 처리해 주면 된다.

The SafeHandle class provides critical finalization of handle resources, preventing handles from being reclaimed prematurely by garbage collection and from being recycled by Windows to reference unintended unmanaged objects.

SafeHandle Class

마치며

패턴은 안전빵이다. 터무니없이 패턴을 적용하지 않는 이상 중간은 간다. 물론 패턴을 완전히 이해하고 사용하면 100점! 패턴의 분석을 통해 본인의 프로그램밍 철학과 통찰력을 넓히면 150점!