[Effective C#] 표준 Dispose패턴을 구현하라

WH_NAM·2024년 2월 28일

Effective C#

목록 보기
17/23
post-thumbnail

Effective C# Item 17

.NET 내부에서는 비관리 리소스를 정리하는 표준화된 패턴을 사용하고 있다. 이는 Dispose 패턴으로 알려져 있다. Dispose 패턴은 가비지 수집기와 연계되어 동작하며 불가피한 경우에만 finalizer를 호출하도록 하여 성능에 미치는 부정적인 영향을 최소화한다. .NET 개발자라면 철저하게 그 내용을 이해해야만 한다.

작업 수행

  • 모든 비관리 리소스를 정리한다.
  • 모든 관리 리소스를 정리한다.
  • 객체가 이미 정리되었음을 나타내기 위한 상태 플래그 설정, 앞서 이미 정리된 객체에 대하여 추가로 정리 작업이 요청될 경우 이 플래그를 확인하여 ObjectDisposed 예외를 발생시킨다.
  • finalizer 호출 회피, 이를 위해 GC.SuppressFinalize(this)를 호출한다.

최상위 베이스 클래스

  • 리소스를 정리하기 위해서 IDisposable 인터페이스를 구현한다.
  • 멤버 필드로 비관리 리소스를 포함하는 경우에 한해 방어적으로 동작할 수 있도록 finalizer를 추가해야 한다.
  • Dispose와 finalizer(존재하는 경우)는 실제 리소스 정리 작업을 수행하는 다른 가상 메서드에 작업을 위임하도록 자성돼야 한다. 파생 클래스가고유의 리로스 정리 작업이 필요한 경우 이 가상 메서드를 재정의할 수 있도록 하기 위함이다.
public class MyBaseClass : IDisposable
{
	// 이미 dispose 되었는지를 나타내는 플래그
	private bool disposed = false;

    // 비관리 리소스를 나타내는 필드
    private IntPtr unmanagedResource;

    public MyBaseClass()
    {
    	// 비관리 리소스 초기화 등의 작업 수행
        unmanagedResource = /* 초기화 작업 */
    }

    // Disposable을 구현
    // 가상 Dispose 메서드를 호출하고
    // finalize를 회피하도록 한다.
    public void Dispose()
    {
    	Dispose(true);
        GC.SuppressFinalize(this);
    }

    // finalizer를 통한 리소스 정리
    ~MyBaseClass()
    {
    	Dispose(false);
    }

    // 가상 Dispose 메서드
    protected virtual void Dispose(bool isDisposing)
    {
    	if (disposed)
        	return;

        if (isDisposing)
        {
        	// 관리 리소스 정리 작업 수행
        }

        // 비관리 리소스 정리 작업 수행
        ReleaseUnmangedResource();

        disposed = true;
    }

    // 비관리 리소스 정리 메서드
    private void ReleaseUnmangedResource()
    {
    	// 비관리 리소스 정리 작업 수행
        // unmanagedResource 해제 동의 작업
    }
}

파생 클래스

  • 파생 클래스가 고유의 리소스 정리 작업을 수행해야 한다면 베이스 클래스에서 정의한 가상 메서드를 재정의한다.
  • 멤버 필드로 비관리 리소스를 포함하는 경우에만 finalizer를 추가해야 한다.
  • 베이스 클래스에서 정의하고 있는 가상 함수를 반드시 재호출해야한다.
public class MyDerivedClass : MyBaseClass
{
	// 자신만의 disposed 플래그
	private bool disposed = false;

    // 파생 클래스에서 추가적인 멤버 필드 및 리소스

    protected ivrtual void Dispose(bool isDisposing)
    {
    	if (disposed)
        return;

        if (isDisposing)
        {
        	// 파생 클래스에서의 관리 리소스 정리 작업 수행
        }

        // 파생 클래스에서의 추가적인 비관리 리소스 정리 작업 수행

        // 베이스 클래스가 자신의 리소스를 정리할 수 있도록 해주어야 한다.
        // 베이스 클래스는 GC.SuppressFinalize()를 호출해야 한다.
        base.Dispose(isDisposing);

        // 파생 클래스의 리소스가 정리되었음을 표시
        disposed = true;
    }

}

주의사항
반드시 비관리 리소스를 포함하는 경우에만 finalizer를 구현해라.
finalizer가 존재하는 것만으로도 상당한 성능상의 손해를 감수해야 한다.

베이스 클래스가 비관리 리소스를 포함하지 않더라도 파생 클래스가 비관리 리소스를 포함할 수 있으므로 이 패턴의 구현부는 그대로 유지하는 편이 낫다.

Dispose 메서드 내에서는 리소스 정리 작업만을 수행하라. Dispose나 finalizer에서 다른 작업을 수행하게 되면 객체의 생명주기와 관련된 심각한 문제를 일으킬 수 있다.

profile
안녕하세요

0개의 댓글