개발/C#

[Effective C#] C# 표준 Dispose 패턴 이해하기

huiyu 2021. 1. 2. 06:00

Implement a dispose pattern -> Link

- .Net Framework 내부에서 비관리 리소스를 정리하는 표준화된 패턴
- 개발자들에게 IDisposable 인터페이스를 통해 리소스를 삭제할 수 있는 기능을 안정적으로 제공한다.
- 비관리 리소스를 명시적으로 정리해야 한다는 사실을 잊거나 인지하지 못한 경우에도 finalizer를 통해 리소스가 정리될 수 있도록 해준다. 

베이스 클래스에서의 작업

- 리소스를 정리하기 위해 IDisposable 인터페이스를 구현한다.
- 멤버 필드로 비관리 리소스를 포함하는 경우에 한해 방어적으로 동작할 수 있도록 반드시 finalizer를 추가한다.
 -> 사용자가 Dispose()메서드를 항상 올바르게 호출할 것이라고 가정할 수 없다. 비관리 리소스가 어떠한 경우에도 누수되지 않도록 올바르게 정리될 수 있도록 finalizer를 구현해야 한다.
- Dispose와 finalizer는 실제 리소스 정리 작업을 수행하는 다른 가상 메서드에 작업을 위임하도록 작성돼야 한다. 파생 클래스가 고유의 리소스 정리 작업이 필요한 경우 이 가상 메서드를 재정의할 수 있도록 하기 위함이다.

파생 클래스에서의 작업

- 파생 클래스가 고유의 리소스 정리 작업을 수행해야 한다면 베이스 클래스에서 정의한 가상 메서드를 재정의한다.
- 멤버 필드로 비관리 리소스를 포함하는 경우에만 finalizer를 추가해야 한다.
- 베이스 클래스에서 정의하고 있는 가상 함수를 반드시 재호출해야한다.

IDisposable Interface

- 비관리 리소스를 정리하는 표준화된 방법이며, Dispose() 하나의 메서드만 제공한다.

1
2
3
4
public interface IDisposable
{
  void Dispose();
}
cs

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

protected로 선언된 가상 헬퍼함수(virtual helper function) 제공

1
protected virtual void Dispose(bool isDisposing);
cs

- 리소스 정리를 위한 공통 작업을 수행하고 파생 클래스에게도 리소스를 정리할 수 있도록 제공
 ->상속받은 클래스에서도 리소스를 해제할 수 있도록 도와주는 코드이다.
- isDisposing이 true일 경우, 관리/비관리 리소스 모두 해제. false인 경우 비관리 리소스만 정리한다.
- 코드의 마지막 부분에는 반드시 베이스 클래스에서 정의하는 Dispose를 호출해야 한다.
* 이 가상함수를 이용하여 finalizer와 Dispose에서 모두 사용할 수 있으며, 파생 클래스에서 이 함수를 재정의하여 자신이 소유한 리소스를 정리할 수 있다.

IDisposable 인터페이스와 virtual DIspose(bool) 예제

Base Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
 
class BaseClass : IDisposable
{
    // To detect redundant calls
    private bool _disposed = false;
 
    ~BaseClass() => Dispose(false);
 
    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
 
    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }
 
        if (disposing)
        {
            // TODO: dispose managed state (managed objects).
        }
 
        // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
        // TODO: set large fields to null.
 
        _disposed = true;
    }
}
cs

Derived class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
 
class DerivedClass : BaseClass
{
    // To detect redundant calls
    bool _disposed = false;
 
    ~DerivedClass() => Dispose(false);
 
    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }
 
        if (disposing)
        {
            // TODO: dispose managed state (managed objects).
        }
 
        // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
        // TODO: set large fields to null.
        _disposed = true;
 
        // Call the base class implementation.
        base.Dispose(disposing);
    }
}
cs

- dispose 여부를 나타내는 고유 플래그는 베이스 클래스와 파생 클래스 각각 갖고 있다. 방어코드를 작성하기 위한 코드로, 플래그를 이중으로 배치하여 베이스 클래스 혹은 파생 클래스의 일부만이 정리된 경우를 피하기 위함이다.

- 위 예제에선 finalizer를 구현하여 비관리 리소스를 해제하게 구현하였다. 이는 비관리 리소스가 존재하는 경우에만 해당하며, 만약 비관리 리소스를 직접 포함하지 않는 경우라면 finalizer는 구현하지 않는 것이 좋다. finalizer의 존재로 성능에 손해볼 수 있기 때문이다. finalizer가 필요없는 경우라면 절대 추가하지 않는다.

 

Dispose pattern 구현 시 주의사항

- Dispose와 finalizer는 방어적으로 작성되어야 한다. Dispose 메서드는 한번 이상 호출될 수 있으므로, 여러 번 호출해도 문제없이 구현해야 한다. 만약 이미 정리된 객체에 대해 호출한 경우 ObjectDisposedException 예외를 발생시키는 것 또한 Dispose 패턴의 규칙이다.

- Dispose 메서드 내에서는 리소스 정리 작업만 수행한다. Dispose나 finalizer에서 다른 작업을 수행하게 되면 객체의 생명주기와 관련된 심각한 문제를 일으킬 수 있다. 만약 finalizer에서 객체에 접근하여 다른 작업을 수행하면 객체는 'reachable'상태로 되어 객체가 죽지 않고 다시 살아날 수있다.

[참고]
- Effective C# 아이템17 : 표준 Dispose 패턴을 구현하라
- MS Document
docs.microsoft.com/ko-kr/dotnet/standard/garbage-collection/implementing-dispose

 

Dispose 메서드 구현

이 문서에서는 .NET에서 코드에 사용되는 비관리형 리소스를 해제하는 Dispose 메서드를 구현하는 방법을 알아봅니다.

docs.microsoft.com

docs.microsoft.com/ko-kr/dotnet/api/system.idisposable.dispose?view=net-5.0

 

IDisposable.Dispose 메서드 (System)

관리되지 않는 리소스의 확보, 해제 또는 다시 설정과 관련된 애플리케이션 정의 작업을 수행합니다.Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

docs.microsoft.com

 

 

728x90
반응형