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
docs.microsoft.com/ko-kr/dotnet/api/system.idisposable.dispose?view=net-5.0
'Software Development > C#' 카테고리의 다른 글
[Effective C#] 지역변수 선언은 var를 사용/var 이해하기 (0) | 2021.01.09 |
---|---|
C# yield 기초 이해하기 (0) | 2021.01.04 |
[Effective C#] C# 정적 클래스 멤버 초기화 유의사항 (0) | 2020.12.31 |
[Effective C#]C# 멤버 초기화 - 할당 구문보다 멤버 초기화를 사용한다. (0) | 2020.12.30 |
[Effective C#] C# 가비지 컬렉터(Garbage Collector) 기초 / finalizer 이해하기 (0) | 2020.12.28 |