[Effective C# 개정판 3판 ] 17장 표준 Dispose 패턴을 구현하라
- C#/Effective C# 책 정리
- 2021. 6. 3. 18:36
1. 참조
- Effective C# <강력한 C# 코드를 구현하는 50가지 전략과 기법, 이펙티브>, 빌 와그너, 김명신, 한빛미디어
- https://docs.microsoft.com/ko-kr/dotnet/standard/garbage-collection/implementing-dispose
2. 소개
- 비관리 리소스 : 메모리가 아닌 자원 을 말하며, 윈도우 핸들, 파일 핸들, 소켓 핸들 등 시스템 자원 을 뜻합니다.
- 반대로 관리 리소스에는 new List() 등, 메모리 처럼 쓰는 자원을 말합니다.
- 이런 비관리 리소스들은 가비지 콜렉터가 아닌 개발자가 직접 관리해줘야 합니다.
- 이미 .NET 프레임워크에는 비관리 리소스를 정리하는 표준화된 패턴을 사용하고 있습니다.
- 이것이 바로 Dispose 패턴 입니다.
3. Dispose 패턴 사용해야 하는 이유
- Dispose 패턴은 비관리 리소스 를 관리하기 위한 패턴입니다.
- 이 패턴을 이용하면 개발자들에게 IDisposable 인터페이스를 통해서 리소스를 삭제할 수 있는 기능을 안정적으로 제공할 수 있습니다.
- 또, 비관리 리소스를 명시적으로 정리해야 한다는 사실을 잊어버리거나 인지하지 못한 경우에도 finalizer 를 통해 올바르게 리소스가 정리될 수 있도록 해줍니다.
- 비관리 리소르를 포함하는 클래스는 반드시 finalizer 는 호출하도록 해야 하지만, 그런 경우가 아니라면 성능에 부정적인 영향을 미치는 것을 최소화합니다.
4. finalizer가 왜 성능에 안좋나?
- finalizer를 구현한 객체는 바로 메모리에서 제거되지 않고 가비지 콜렉터가 finalizer 큐 라는 곳에 이 객체들의 참조를 삽입해 둡니다.
- 이후에 finalizer 스레드 라는 특별한 스레드를 이용하여 finalizer를 순차적으로 호출 합니다.
- 이 객체들은 가비지 콜렉터에 의해서 제거될 수 있는 대상으로 간주합니다.
- 때문에, 다른 객체에 비해서 상대적으로 메모리에 오래 살아 남습니다.
- 즉, 메모리에 오래 남는 타입이고 한 세대 높아지고 가비지 콜렉터가 finalizer 객체 검사하는 과정에서 연산을 잡아 먹으면서 성능적으로 비효율적입니다.
5. IDisposable.Dispose() 메서드 수행
- IDisposable.Dispose() 메서드는 다음 네가지 작업을 무조건 수행합니다.
- 모든 비관리 리소스를 정리 합니다.
- 모든 관리 리소스를 정리 합니다.
- 객체가 이미 정리되었음을 나타내기 위한 상태 플래그를 설정합니다. 앞에서 이미 정리된 객체에 대하여 추가로 정리 작업이 요청될 경우, 이 플래그를 확인하여 ObjectDisposed 예외를 발생시킵니다.
- finalizer 호출 회피. 이를 위해 GC.SupperessFinalize(this) 호출
public interface IDisposable
{
void Dispose();
}
6. Dispose() 취약점
- Dispose() 패턴도 취약한 부분이 있습니다.
- 파생 클래스의 메모리 해제 후, 베이스 클래스의 메모리 해제 문제입니다.
- 이 경우엔, protected로 선언된 virtual void Dispose(bool isDisposing); 이라는 함수를 통해 해결이 가능합니다.
6.1 Dispose 패턴 예제 코드
using System;
namespace Chapter17
{
class Program
{
static void Main(string[] args)
{
}
}
public class BaseClass : IDisposable
{
// Dispose 체크 플래그 변수
private bool alreadDisposed = false;
// IDisposable을 구현
// 가상 Dispose 메서드를 호출하고
// finalize를 회피하도록 합니다.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// 가상 Dispose 메서드
protected virtual void Dispose(bool isDisposing)
{
// Dispose 는 한 번만 수행되도록 합니다.
if (alreadDisposed)
return;
if(isDisposing)
{
// 해당 구문에 관리 리소스 정리합니다.
}
}
public void ExampleMethod()
{
if(alreadDisposed)
{
throw new ObjectDisposedException("BaseClass", "Called Example Method on Disposed Object");
}
}
}
public class DerivedClass : BaseClass
{
// 자신만의 dispose 플래그
private bool disposed = false;
protected override void Dispose(bool isDisposing)
{
// Dispose는 한 번만 수행되도록 한다.
if (disposed)
return;
if(isDisposing)
{
// 여기서 관리 리소스 관리 합니다.
}
// 여기서 비관리 리소스 정리합니다.
// 베이스 클래스가 자신의 리소스를 정리할 수 있도록 해주어야 합니다.
// 베이스 클래스는 GC.SuppressFinalize()를 호출해야 합니다.
base.Dispose(isDisposing);
// 파생 클래스의 리소스가 정리되었음을 표시합니다.
disposed = true;
}
}
}
6. 정리
- 새롭게 작성할 타입이 비관리 리소스를 포함하거나 혹은 IDisposable을 구현한 다른 타입을 포함해야 하는 경우에만 finalizer 를 제한적으로 구현합니다.
- IDisposable 인터페이스만 필요하고 finalizer를 구현할 필요가 없는 경우라 하더라도 표준 Dispose 패턴 의 구조는 온전히 유지하는 것이 좋습니다.
- 온전히 유지하지 않으면 파생 클래스에서 표준 Dispose 패턴을 구현하는 것이 복잡해 지는 단점이 있습니다.
728x90
'C# > Effective C# 책 정리' 카테고리의 다른 글
[Effective C# 21장] 타입 매개변수가 IDisposable을 구현한 경우를 대비하여 제네릭 클래스를 작성하라 (0) | 2021.07.14 |
---|---|
[Effective C# 개정판 3판 19장] 런타임에 타입을 확인하여 최적의 알고리즘을 사용하라 (0) | 2021.06.11 |
[Effective C# item 2] const보다는 readonly가 좋다 (0) | 2021.05.22 |
[Effective C# item 15] 불필요한 객체를 만들지 말라 (0) | 2021.05.21 |
[Effective C# Item 5] 문화권별로 다른 문자열을 생성하려면 FormattableString을 사용하라. (0) | 2021.05.12 |
이 글을 공유하기