다음 본문은 도서 이펙티브 C# (빌 와그너)에서 나오는 주제를 다룹니다.
Dispose 패턴
객체가 비관리 리소스를 포함하는 경우 정리 작업은 굉장히 중요하다.
.NET Framework 내부에서는 이에 대해 표준화된 패턴을 사용하고 있으며,
성능에 미치는 부정적인 영향을 최소화 하기 위해서는 규칙에 따라 리소스를 정리해야 한다.
최상위 베이스 클래스는 다음의 작업을 수행해야 한다.
1) 리소스를 정리하기 위해서 IDisposable 인터페이스를 구현해야 한다.
2) 멤버 필드로 비관리 리소스를 포함하는 경우 방어적으로 동작할 수 있도록 finalizer를 추가해야 한다.
3) Dispose와 finalizer는 실제 리소스 정리 작업을 수행하는 다른 가상 메서드에 작업을 위임하도록 작성돼야 한다. 파생 클래스가 고유의 리소스 정리 작업이 필요한 경우 이 가상 메서드를 재정의 할 수 있도록 하기 위함이다.
파생 클래스는 다음 작업을 수행해야 한다.
1) 파생 클래스가 고유의 리소스 정리 작업을 수행해야 한다면 베이스 클래스에서 정의한 가상 메서드를 재정의한다.
2) 멤버 필드로 비관리 리소스를 포함하는 경우에만 finalizer를 추가해야 한다.
3) 베이스 클래스에서 정의하고 있는 가상 함수를 반드시 재 호출해야한다.
finalizer
- 가비지 수집대상으로 체크된 객체에 finalizer가 없다면 즉시 메모리에서 제거된다.
- finalizer를 가진 객체는 finalizer큐에 참조되어 각각의 finalier를 순차적으로 호출하게 된다.
- finalizer가 호출된 객체는 메모리에서 제거될 수 있는 객체로 체크되어 수집되길 기다린다.
- 최초 수집되지 못하였으니 가비지 수집에서 한 세대가 높아지게 되고, 상대적으로 오래 메모리에 남게 된다.
IDisposable 인터페이스
비관리 리소스를 정리하는 표준화된 방법이며, 한 개의 메서드만을 가진다.
IDisposable을 통해 finalize 과정으로 인해 발생하는 불필요한 비용을 피할 수 있다.
public interface IDisposable
{
void Dispose();
}
IDisposable.Dispose() 메서드는 다음 네 가지의 작업을 반드시 수행해야 한다.
1. 모든 비관리 리소스를 정리한다.
2. 모든 관리 리소스를 정리한다.
3. 객체가 정리되었음을 나타내기 위한 플래글 설정. 이 플래그를 통해 이미 정리된 객체에 추가로 정리 작업이 요청되면 ObjectDisposed 예외를 발생시킨다.
4. finalizer 호출 회피. 이를 위해 GC.SupperessFinalize(this)를 호출한다.
가상 헬퍼 함수
- finalizer와 IDisposable 인터페이스에서 하는 일이 중복될 여지가 있으며,
인터페이스는 가상 함수가 아니기 때문에 재정의하면 예상대로 동작하지 않는다.
- 이 때문에 표준 Dispose 패턴에서는 가상 헬퍼 함수를 정의한다.
protected virtual void Dispose(bool isDisposing);
- 코드의 마지막에는 반드시 베이스 클래스가 정의한 Dispose(bool)을 호출해야 한다.
- true : 관리 리소스, 비관리 리소스를 모두 정리한다
- false : 비관리 리소스만 정리한다.
public class MyResource : System.IDisposable
{
private bool alreadyDisposed = false;
public void Dispose()
{
Dispose(true);
System.GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool isDisposing)
{
if (alreadyDisposed)
return;
if(isDisposing)
{
// 관리 리소스 정리
}
// 비관리 리소스 정리
alreadyDisposed = true;
}
}
public class DerivedResource : MyResource
{
private bool disposed = false; // 자신만의 disposed 플래그
protected override void Dispose(bool isDisposing)
{
if (disposed)
return;
if (isDisposing)
{
// 관리 리소스 정리
}
// 비관리 리소스 정리
base.Dispose(isDisposing); // 베이스 클래스가 GC.SupressFinalize()를 호출한다.
disposed = true;
}
}
주의점
- Dispose 메서드는 여러 번 호출하더라도 반드시 동일하게 동작하도록 구현해야 한다.
- Dispose, finalizer 내에서는 리소스 정리 작업만을 수행하라. 객체의 생명주기와 관련된 문제를 일으킬 수 있다.
- 클래스가 비관리 리소스를 포함하는 경우에는 반드시 finalizer를 구현해야 하며,
비관리 리소스를 포함하지 않는다면 finalizer를 구현하지 마라
결론
- 비관리 리소스를 사용하는 객체는 표준 Dispose 패턴을 사용해야 성능 문제를 막을 수 있다.
'🌍 C# Study > 이펙티브 C#' 카테고리의 다른 글
[19] 런타임에 타입을 확인하여 최적의 알고리즘을 사용하라 (0) | 2021.04.26 |
---|---|
[18] 반드시 필요한 제약조건만 설정하라 (0) | 2021.04.26 |
[16] 생성자 내에서는 절대로 가상 함수를 호출하지 말라 (0) | 2021.04.26 |
[15] 불필요한 객체를 만들지 말라 (0) | 2021.04.25 |
[14] 초기화 코드가 중복되는 것을 최소화하라 (0) | 2021.04.25 |