IDisposable 인터페이스
비관리 리소스를 해제하는 방법을 제공한다.
public interface IDisposable
C#에서는 GC가 참조 형식의 객체의 메모리를 자동으로 관리하며, 더 이상 참조되지 않아 메모리가 해제될 때 비관리 리소스를 갖고있는 객체가 해당 인터페이스를 통해 직접 해제할 수 있는 메커니즘을 제공한다.
어느 시점에 해당 리소스가 해제될 지 (GC가 언제 릴리즈할지) 정확한 예측을 할 수는 없으며, GC는 관리되지 않는 리소스(윈도우 핸들이나, 파일 핸들, 스트림 등)를 알 길이 없다.
GC와 더불어 사용자가 직접 판단하에 해당 객체가 더 이상 필요없다고 판단할 경우 직접 Dispose()를 호출하여 비관리 리소스를 해제할 수 있다.
IDisposable.Dispose의 구현은 인스턴스가 더 이상 필요하지 않을 때 호출되므로 SafeHandle과 같은 관리 객체를 래핑해서 사용하거나 Finalizer를 구현해서 직접 비관리 리소스를 해제하도록 유도해야한다.
class DisposeExample
{
public class MyResource : IDisposable
{
private IntPtr Handle; // 외부 비관리 리소스 핸들
private Component component = new Component(); // 관리 리소스
private bool disposed = false; // Dispose가 호출되었는지 확인한다.
public MyResource(IntPtr _Handle)
{
Handle = _Handle;
}
public void Dispose()
{
Dispose(disposing: true);
// 사용자가 직접 Dispose()를 호출했기 때문에
// GC는 finalizer를 호출할 필요가 없다.
// GC는 finalizer를 구현한 인스턴스에 대해 finalizer를 호출한다.
GC.SuppressFinalize(this);
}
// disposing이 true인 경우 사용자가 직접 호출하여
// 해당 인스턴스의 관리/비관리 리소스를 해제하고 있는 것이다.
// disposing이 false인 경우, finalizer에 의해 호출되어
// 다른 객체를 더 이상 참조하면 안되기 때문에 오직 비관리 리소스만 해제되어야한다.
protected virtual void Dispose(bool disposing)
{
// 이미 리소스가 해제 되었는지 확인
if (disposed == true)
{
return;
}
// 실제로 사용자가 호출한 경우에만
// 관리 리소스를 호출한다.
if(disposing)
{
component.Dispose();
}
// 비관리 리소스를 해제하기 위해
// 올바른 함수를 호출한다.
CloseHandle(Handle);
Handle = IntPtr.Zero;
// 이제 해당 인스턴스는 모두 해제되었다.
disposed = true;
}
// 비관리 리소스를 해제하는 닷넷의 Interop 인터페이스를 사용한다.
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
// C# finalizer는 비관리 리소스만 해제해야한다.
~MyResource() => Dispose(disposing: false);
}
}
Dispose 인터페이스의 구현
1. Dispose(bool disposing) 사용자가 직접 호출하고 있는 지 혹은 finalizer에 의해 호출되고 있는지 알기 위해 disposing을 전달한다.
2. Dispose() 내부에서는 사용자가 직접 호출한 경우(disposing : true) 관리/비관리 리소스를 모두 해제시킬 수 있다.
3. Finalizer에 의해 호출된 경우(disposing : false) 참조를 만들면 문제가 생길 수 있으므로 비관리 리소스만 해제하도록 한다.
4. disposed 부울 변수는 해당 Dispose() 함수가 호출된 적이 있는지 확인한다. 한번 호출된 적이 있다면 두 번이상 호출할 필요가 없다.
using 키워드
using 키워드는 비관리 리소스를 해제해야하는 인스턴스가 예외 안전하게 항상 비관리 리소스를 해제하도록 try-finally를 컴파일러가 IL 언어로 만들어주도록 한다.
using System;
using System.IO;
using System.Text.RegularExpressions;
public class WordCount
{
private String filename = String.Empty;
private int nWords = 0;
private String pattern = @"\b\w+\b";
public WordCount(string filename)
{
if (! File.Exists(filename))
throw new FileNotFoundException("The file does not exist.");
this.filename = filename;
string txt = String.Empty;
using (StreamReader sr = new StreamReader(filename)) {
txt = sr.ReadToEnd();
}
nWords = Regex.Matches(txt, pattern).Count;
/*
StreamReader sr = null;
try {
sr = new StreamReader(filename);
txt = sr.ReadToEnd();
}
finally {
if (sr != null) sr.Dispose();
}
nWords = Regex.Matches(txt, pattern).Count;
*/
}
public string FullName
{ get { return filename; } }
public string Name
{ get { return Path.GetFileName(filename); } }
public int Count
{ get { return nWords; } }
}
Base-Derived 상속 관계에서 올바르게 Dispose를 구현하기
Base 클래스에서는 상속을 생각하지 않은 경우와 마찬가지로 인터페이스를 구현해주면 된다.
- Dispose()에서 Dispose(disposing : true)를 호출한다. GC의 Finalizer 호출을 막는다.
- Dispose(bool disposing) : 관리/비관리 리소스를 해제한다.
- Finalizer : Dispose(false)를 호출한다.
using System;
class BaseClass : IDisposable
{
// Flag: Has Dispose already been called?
bool disposed = false;
// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing) {
// Free any other managed objects here.
//
}
// Free any unmanaged objects here.
//
disposed = true;
}
~BaseClass()
{
Dispose(disposing: false);
}
}
Derived 파생 클래스에서는 Finalizer를 정의하지 않는다. 대신 Dispose(bool disposing)을 상속해서 관리/비관리 리소스를 해제한 뒤에 base.Dispose(disposing)을 호출하도록한다.
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
class DerivedClass : BaseClass
{
// Flag: Has Dispose already been called?
bool disposed = false;
// Instantiate a SafeHandle instance.
SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);
// Protected implementation of Dispose pattern.
protected override void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing) {
handle.Dispose();
// Free any other managed objects here.
//
}
// Free any unmanaged objects here.
//
disposed = true;
// Call base class implementation.
base.Dispose(disposing);
}
}
참고 : https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=net-5.0
'C# > C# 인터페이스' 카테고리의 다른 글
C# 인터페이스 vs 추상 클래스 (0) | 2022.04.09 |
---|---|
C# 공변성과 반공변성 Covariance / Contravariance (0) | 2021.11.20 |
IComparable/IComparer/IEquatable : 비교와 관련된 인터페이스 (0) | 2021.11.13 |
ICollection : 컬렉션 인터페이스 (0) | 2021.11.13 |
IEnumerable / IEnumerator : 순회 가능한 컬렉션과 순회하는 방법 (0) | 2021.11.13 |