title: "**C#과 C++/CLI의 Finalize, IDisposable Pattern 차이**"
description: "**C#과 C++/CLI의 Finalize, IDisposable Pattern 차이**"
cleanUrl: /sw-engineer/finalize-dispose-pattern
ogImage: ""
floatFirstTOC: right

좀 긴가민가했던 .NET core 관련 내용을 정리하다가, 올바른 unmanaged 리소스 청소법을 위한 '지저분하기 짝이 없는, 그러나 반드시 알아야 하는' Finalize, IDisposable에 다다랐는데, 흥미롭게도 C# 쪽 pattern과 C++/CLI 쪽 pattern이 (적어도 표면 상으로는) 완연히 다르다는 사실을 발견.

본 사항은 정상적 application 구현을 위해서는 반드시 숙지해야 할 내용인데, 언어 별로 그리도 달라서야 원. 게다가, C++/CLI 쪽 MSDN 설명은 뭔가 하나 빠진 듯 하여 다 읽고 나서도 제대로 이해가 가질 않는다. 언어 별로 따로 익혀야 하는 것도 거시기한데, 설명이라도 제대로 해야지.

먼저, C#쪽 pattern. MSDN에 떡하니 올라와 있는 정형화된 pattern이다.

// 기반 클래스에서의 구현 pattern
public class Base: IDisposable
{
   public void Dispose()
   {
      Dispose(true);

		  // GC가 Finalize를 호출하지 않도록 함(중복호출 배제)
      GC.SuppressFinalize(this);
   }
 
   // disposing 플래그를 통해 Finalize에서 managed 리소스를 정리하지 않도록(해당 리소스는 GC가 정리할 것임)
   protected virtual void Dispose(bool disposing)
   {
      if (disposing)
      {
         // Managed 리소스 정리
      }
      // Unmanaged 리소스 정리
   }

   // C# 소멸자. Finalize 메서드임
   ~Base()
   {
      // 단순히 Dispose(false).
      Dispose (false);
   }
}

// 파생 클래스에서의 구현 pattern
public class Derived: Base
{  
   protected override void Dispose(bool disposing)
   {
      if (disposing)
      {
         // managed 리소스의 정리
      }
      // Unmanaged 리소스 정리. 부모 개체의 리소스를 정리하도록
      base.Dispose(disposing);
   }

   // 파생 클래스에서는 소멸자 정의를 하지 않음(부모 소멸자에서 재정의된 Dispose를 호출할 것이므로)
}

암만 봐도 복잡하기 짝이 없는 패턴. 하지만 이보다 더 단순한 패턴을 내 머리로 만들어낼 궁리는 안한다(나올 가능성도 거의 없겠지만). 다음은 상기 사항에 대한 C++/CLI 쪽 pattern. 이 역시 MSDN에 명시된 내용이다.

ref class A 
{
  // Dispose()에 해당하는 소멸자. delete를 통해 명시적 호출 가능.
  // Native C++의 가상 소멸자와 동일한 행동 양식(스택 기반 semantic 개체 생성 시, 자동 호출됨)
	~A() 
	{
      // managed 리소스 제거
      // ...

      // finalizer를 통한 unmanaged 리소스 제거
      this->!A();
   }

   // Finalize에 해당하는 Finalizer
	!A() 
	{
		// unmanaged 리소스 제거
  }
};

MSDN에는 몇몇 설명으로 위 C++/CLI의 패턴을 설명하다 마무리 짓는데, 상당히 난감해진다. 패턴은 왜 달라지는지, 달라지면서 없어진 GC.SupressFinalize(), Dispose(bool)은 어디로 갔는지 등에 대한 설명은 없거나 부실하다. 게다가 파생 클래스에 대한 언급은 아예 없어 과연 위 내용이 올바른 내용인가하는 의심까지 들 정도.

다음은 위 패턴에 대한 MSDN에 없는 내용으로서, 이와 같은 의문을 해소할 key가 되는 사항이다(C++/CLI의 기본 개념에 대해서는 MSDN 및 C++/CLI 소개글(C++/CLI: .NET 프레임워크 프로그래밍을 위한 가장 강력한 언어(1/2), C++/CLI: .NET 프레임워크 프로그래밍을 위한 가장 강력한 언어(2/2)) 참조.

  1. C++/CLI에서의 소멸자는 virtual 키워드가 없더라도 무조건 가상 함수이다.
  2. finalizer의 가시성 범위는 accessor가 있건 없건 private이다.
  3. destructor와 finalizer가 IDisposable::Dispose()와 Finalize()를 완전 대체하지는 않는다. 컴파일러는 IL 코드 내에 Dispose()와 Finalize()를 따로 삽입하며, 각기 내부에서 destructor와 finalizer를 적절히 호출한다.
  4. destructor가 호출되면 finalizer는 호출되지 않는다. 이는 IL 코드 내 Dispose() 구현에서 GC.SupressFinalizer()를 호출하기 때문이다.
  5. 위 코드를 기반으로 한 컴파일된 IL 코드는 C# 버전과 거의 흡사하다(Dispose(bool)을 통한 파생 클래스에서의 리소스 정리 등).
  6. 위와 같은 내용을 기반으로, 파생 클래스에서 역시 위 패턴과 동일하게 작성하면 된다(destructor 또는 Finalizer 등에서 base 클래스의 destructor/finalizer 명시적 호출 등 부가적 행동 불필요).

위의 결론은 김형준님의 C++/CLI의 Dispose Pattern에 대한 고찰과 유사한 실험 및 생성된 IL 코드 분석을 통해 이루어졌다.