Dispose 메서드 구현
Dispose 메서드는 주로 관리되지 않는 리소스를 해제하기 위해 구현됩니다. IDisposable 구현인 인스턴스 멤버를 사용하는 경우 일반적으로 Dispose 호출을 연계하는 것이 일반적입니다. Dispose구현하는 다른 이유가 있습니다. 예를 들어 할당된 메모리를 해제하거나, 컬렉션에 추가된 항목을 제거하거나, 획득한 잠금 해제를 신호로 표시합니다.
.NET 가비지 수집기 관리되지 않는 메모리를 할당하거나 해제하지 않습니다. 삭제 패턴이라고 하는 개체를 삭제하는 패턴은 개체의 수명 동안 순서를 적용합니다. 삭제 패턴은 IDisposable 인터페이스를 구현하는 개체에 사용됩니다. 이 패턴은 가비지 수집기가 관리되지 않는 개체를 회수할 수 없기 때문에 파일 및 파이프 핸들, 레지스트리 핸들, 대기 핸들 또는 관리되지 않는 메모리 블록에 대한 포인터와 상호 작용하는 경우에 일반적입니다.
리소스가 항상 적절하게 정리되도록 하기 위해서는, Dispose 메서드가 예외를 발생시키지 않고 여러 번 호출이 가능하도록 idempotent해야 합니다. 또한 후속 Dispose 호출은 아무 것도 수행하지 않아야 합니다.
GC.KeepAlive 메서드에 제공된 코드 예제는 개체 또는 해당 멤버에 대한 관리되지 않는 참조가 계속 사용 중인 동안 가비지 수집으로 인해 종료자가 실행되는 방법을 보여줍니다. 현재 루틴의 시작부터 이 메서드가 호출되는 지점까지 개체를 가비지 수집할 수 없도록 하려면 GC.KeepAlive 활용하는 것이 합리적일 수 있습니다.
팁
종속성 주입과 관련하여 IServiceCollection서비스에 대한 등록 시, 서비스 수명는 사용자를 대신하여 암시적으로 관리됩니다. IServiceProvider 및 해당 IHost 리소스 정리를 오케스트레이션합니다. 특히 IDisposable 및 IAsyncDisposable 구현은 지정된 수명이 끝날 때 올바르게 삭제됩니다.
자세한 내용은 .NET의 종속성 주입을 참조하세요.
안전한 핸들
객체의 파이널라이저 코드를 작성하는 것은 복잡한 작업이며, 올바르게 수행되지 않으면 문제를 일으킬 수 있습니다. 따라서 종료자를 구현하는 대신 System.Runtime.InteropServices.SafeHandle 개체를 생성하는 것이 좋습니다.
System.Runtime.InteropServices.SafeHandle는 관리되지 않는 리소스를 식별하는 System.IntPtr을 래핑하는 추상적인 관리 형식입니다. Windows에서는 핸들을 식별하고 Unix에서는 파일 설명자를 식별할 수 있습니다.
SafeHandle
SafeHandle
삭제되거나 SafeHandle
대한 모든 참조가 삭제되고 SafeHandle
인스턴스가 종료될 때 이 리소스가 한 번만 해제되도록 하는 데 필요한 모든 논리를 제공합니다.
System.Runtime.InteropServices.SafeHandle 추상 기본 클래스입니다. 파생 클래스는 다양한 종류의 핸들에 대한 특정 인스턴스를 제공합니다. 이러한 파생 클래스는 잘못된 것으로 간주되는 System.IntPtr 값과 핸들을 실제로 해제하는 방법을 확인합니다. 예를 들어, SafeFileHandle는 SafeHandle
에서 파생되어 열린 파일 핸들/설명자를 식별하는 IntPtrs
를 래핑하고, SafeHandle.ReleaseHandle() 메서드를 재정의하여 이를 닫습니다(Unix의 close
함수 또는 Windows의 CloseHandle
함수를 통해). 관리되지 않는 리소스를 생성하는 .NET 라이브러리의 대부분의 API는 원시 포인터를 전달하지 않고 SafeHandle
으로 래핑하여 필요에 따라 해당 SafeHandle
을 반환합니다. 관리되지 않는 구성 요소와 상호 작용하고 관리되지 않는 리소스에 대한 IntPtr
를 받는 경우, 이를 래핑할 수 있는 고유한 SafeHandle
형식을 만들 수 있습니다. 따라서SafeHandle
형식이 아닌 형식 중에서 종료자를 구현해야 하는 경우는 거의 없습니다. 대부분의 삭제 가능한 패턴 구현은 다른 관리되는 리소스만 래핑하며, 그 중 일부는 SafeHandle
개체일 수 있습니다.
Microsoft.Win32.SafeHandles 네임스페이스의 다음 파생 클래스는 안전한 핸들을 제공합니다.
수업 | 보유하는 리소스 |
---|---|
SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
파일, 메모리 매핑된 파일 및 파이프 |
SafeMemoryMappedViewHandle | 메모리 보기 |
SafeNCryptKeyHandle SafeNCryptProviderHandle SafeNCryptSecretHandle |
암호화 구문 |
SafeRegistryHandle | 레지스트리 키 |
SafeWaitHandle | 대기 핸들 |
Dispose() 및 Dispose(bool)
IDisposable 인터페이스에는 매개 변수가 없는 단일 메서드 Dispose구현해야 합니다. 또한 봉인되지 않은 클래스에는 Dispose(bool)
오버로드 메서드가 있어야 합니다.
메서드 서명은 다음과 같습니다.
-
public
가상 구현이 아님(NotOverridable
은 Visual Basic,IDisposable.Dispose 구현). -
protected virtual
(Visual Basic에서는Overridable
)Dispose(bool)
.
Dispose() 메서드
Visual Basic에서는 가상이 아닌NotOverridable
, public
, 매개 변수가 없는 Dispose
메서드가 (형식의 소비자에 의해) 더 이상 필요하지 않을 때 호출됩니다. 이 메서드의 목적은 관리되지 않는 리소스를 해제하고, 일반 정리를 수행하며, 종료자가 있는 경우 종료자가 실행될 필요가 없음을 나타내는 것입니다. 관리되는 개체와 연결된 실제 메모리를 해제하는 것은 항상 가비지 수집기도메인입니다. 이 때문에 다음과 같은 표준 구현이 있습니다.
public void Dispose()
{
// Dispose of unmanaged resources.
Dispose(true);
// Suppress finalization.
GC.SuppressFinalize(this);
}
Public Sub Dispose() _
Implements IDisposable.Dispose
' Dispose of unmanaged resources.
Dispose(True)
' Suppress finalization.
GC.SuppressFinalize(Me)
End Sub
Dispose
메서드는 모든 개체 정리를 수행하므로 가비지 수집기는 더 이상 개체의 Object.Finalize 재정의를 호출할 필요가 없습니다. 따라서 SuppressFinalize 메서드를 호출하면 가비지 수집기가 종료자를 실행하지 않게 됩니다. 형식에 종료자가 없으면 GC.SuppressFinalize 호출이 적용되지 않습니다. 실제 정리는 Dispose(bool)
메서드 오버로드에 의해 수행됩니다.
Dispose(bool) 메서드 오버로드
오버로드에서 disposing
매개 변수는 메서드 호출이 Dispose 메서드(값이 true
) 또는 종료자(해당 값이 false
)에서 오는지 여부를 나타내는 Boolean.
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// Dispose managed state (managed objects).
// ...
}
// Free unmanaged resources.
// ...
_disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
If disposed Then Exit Sub
If disposing Then
' Free managed resources.
' ...
End If
' Free unmanaged resources.
' ...
disposed = True
End Sub
중요하다
종료자에서 호출될 때 disposing
매개 변수는 false
이어야 하고, IDisposable.Dispose 메서드에서 호출될 때는 true
이어야 합니다. 즉, 결정적으로 호출될 때는 true
이고 비결정적으로 호출될 때는 false
입니다.
메서드 본문은 다음 세 가지 코드 블록으로 구성됩니다.
개체가 이미 삭제된 경우 조건부 반환을 위한 블록입니다.
관리되는 리소스를 해제하는 조건부 블록입니다. 이 블록은
disposing
값이true
경우 실행됩니다. 관리 해제되는 리소스에는 다음이 포함될 수 있습니다.- IDisposable을 구현하는 관리 객체입니다. 조건부 블록을 사용하여 Dispose 구현(cascade dispose)을 호출할 수 있습니다. 파생 클래스의 System.Runtime.InteropServices.SafeHandle 사용하여 관리되지 않는 리소스를 래핑한 경우 여기서 SafeHandle.Dispose() 구현을 호출해야 합니다.
- 많은 양의 메모리를 사용하거나 부족한 리소스를 사용하는 관리되는 개체입니다. 대용량 관리되는 개체 참조를
null
에 할당하여 더욱 연결할 수 없도록 합니다. 이렇게 하면 비결정적으로 회수된 경우보다 더 빠르게 릴리스됩니다.
관리되지 않는 리소스를 해제하는 블록입니다. 이 블록은
disposing
매개 변수의 값에 관계없이 실행됩니다.
메서드 호출이 종료자에서 오는 경우 관리되지 않는 리소스를 해제하는 코드만 실행해야 합니다. 구현자는 잘못된 경로가 삭제되었을 수 있는 관리되는 개체와 상호 작용하지 않도록 해야 합니다. 이는 종료 중에 가비지 수집기가 관리되는 개체를 삭제하는 순서가 비결정적이므로 중요합니다.
Cascade dispose 호출
클래스가 필드 또는 속성을 소유하고 해당 형식이 IDisposable구현하는 경우 포함하는 클래스 자체도 IDisposable구현해야 합니다.
IDisposable 구현을 인스턴스화하고 인스턴스 멤버로 저장하는 클래스도 해당 정리를 담당합니다. 이렇게 하면 참조된 삭제 가능한 형식에 Dispose 메서드를 통해 결정적으로 정리를 수행할 수 있는 기회가 제공됩니다. 다음 예제에서 클래스는 sealed
(또는 Visual Basic에서는 NotInheritable
)입니다.
using System;
public sealed class Foo : IDisposable
{
private readonly IDisposable _bar;
public Foo()
{
_bar = new Bar();
}
public void Dispose() => _bar.Dispose();
}
Public NotInheritable Class Foo
Implements IDisposable
Private ReadOnly _bar As IDisposable
Public Sub New()
_bar = New Bar()
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
_bar.Dispose()
End Sub
End Class
팁
- 클래스에 IDisposable 필드 또는 속성이 있지만 소유하지 클래스가 개체를 만들지 않는 경우 클래스는 IDisposable구현할 필요가 없습니다.
- 종료자에서
null
-checking을 수행할 수 있는 경우가 있습니다(종료자가 호출한Dispose(false)
메서드 포함). 주된 이유 중 하나는 인스턴스가 완전히 초기화되었는지 확실하지 않은 경우입니다(예: 생성자에서 예외가 throw될 수 있습니다).
삭제 패턴 구현
봉인되지 않은 모든 클래스(또는 NotInheritable
수정되지 않은 Visual Basic 클래스)는 상속될 수 있으므로 잠재적인 기본 클래스로 간주되어야 합니다. 잠재적인 기본 클래스에 대한 삭제 패턴을 구현하는 경우 다음을 제공해야 합니다.
-
Dispose(bool)
메서드를 호출하는 Dispose 구현입니다. - 실제 정리를 수행하는
Dispose(bool)
메서드입니다. - 관리되지 않는 리소스를 감싸는 SafeHandle의 파생 클래스(권장) 또는 Object.Finalize 메서드를 재정의하는 방법 중 하나입니다. SafeHandle 클래스는 종료자를 제공하므로 직접 작성할 필요가 없습니다.
중요하다
기본 클래스는 관리되는 개체만 참조하고 삭제 패턴을 구현할 수 있습니다. 이러한 경우 종료자가 필요하지 않습니다. 종료자는 관리되지 않는 리소스를 직접 참조하는 경우에만 필요합니다.
다음은 안전 핸들을 사용하는 기본 클래스에 대한 삭제 패턴을 구현하는 일반적인 예입니다.
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
public class BaseClassWithSafeHandle : IDisposable
{
// To detect redundant calls
private bool _disposedValue;
// Instantiate a SafeHandle instance.
private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);
// 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 (!_disposedValue)
{
if (disposing)
{
_safeHandle?.Dispose();
_safeHandle = null;
}
_disposedValue = true;
}
}
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices
Public Class BaseClassWithSafeHandle
Implements IDisposable
' To detect redundant calls
Private _disposedValue As Boolean
' Instantiate a SafeHandle instance.
Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)
' Public implementation of Dispose pattern callable by consumers.
Public Sub Dispose() _
Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
' Protected implementation of Dispose pattern.
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
_safeHandle?.Dispose()
_safeHandle = Nothing
End If
_disposedValue = True
End If
End Sub
End Class
메모
이전 예제에서는 SafeFileHandle 개체를 사용하여 패턴을 보여 줍니다. SafeHandle 파생된 모든 개체를 대신 사용할 수 있습니다. 이 예제에서는 SafeFileHandle 개체를 제대로 인스턴스화하지 않습니다.
기본 클래스에서 Object.Finalize을 재정의하는 경우, dispose 패턴을 구현하기 위한 일반적인 패턴은 다음과 같습니다.
using System;
public class BaseClassWithFinalizer : IDisposable
{
// To detect redundant calls
private bool _disposedValue;
~BaseClassWithFinalizer() => 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 (!_disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_disposedValue = true;
}
}
}
Public Class BaseClassWithFinalizer
Implements IDisposable
' To detect redundant calls
Private _disposedValue As Boolean
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
' Public implementation of Dispose pattern callable by consumers.
Public Sub Dispose() _
Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
' Protected implementation of Dispose pattern.
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects)
End If
' TODO free unmanaged resources (unmanaged objects) And override finalizer
' TODO: set large fields to null
_disposedValue = True
End If
End Sub
End Class
팁
C#에서는 Object.Finalize재정의하지 않고 종료자제공하여 종료를 구현합니다. Visual Basic에서는 Protected Overrides Sub Finalize()
사용하여 종료자를 만듭니다.
파생 클래스에 대한 삭제 패턴 구현
IDisposable 인터페이스를 구현하는 클래스에서 파생된 클래스는 IDisposable.Dispose 기본 클래스 구현이 파생 클래스에 의해 상속되므로 IDisposable구현하면 안 됩니다. 대신 파생 클래스를 정리하려면 다음을 제공합니다.
- 기본 클래스 메서드를 재정의하고 파생 클래스의 실제 정리를 수행하는
protected override void Dispose(bool)
메서드입니다. 또한 이 메서드는 삭제 상태(bool disposing
매개 변수)를 인수로 전달하는base.Dispose(bool)
(Visual Basic의MyBase.Dispose(bool)
) 메서드를 호출해야 합니다. - 관리되지 않는 리소스를 래핑하는 SafeHandle 파생된 클래스(권장) 또는 Object.Finalize 메서드에 대한 재정의입니다.
SafeHandle 클래스는 종료자를 제공하여 직접 종료자를 코딩할 필요가 없게 해 줍니다. 종료자를 제공하는 경우에는
Dispose(bool)
오버로드를false
인수를 사용하여 호출해야 합니다.
다음은 안전 핸들을 사용하는 파생 클래스에 대한 삭제 패턴을 구현하기 위한 일반적인 패턴의 예입니다.
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
public class DerivedClassWithSafeHandle : BaseClassWithSafeHandle
{
// To detect redundant calls
private bool _disposedValue;
// Instantiate a SafeHandle instance.
private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);
// Protected implementation of Dispose pattern.
protected override void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_safeHandle?.Dispose();
_safeHandle = null;
}
_disposedValue = true;
}
// Call base class implementation.
base.Dispose(disposing);
}
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices
Public Class DerivedClassWithSafeHandle
Inherits BaseClassWithSafeHandle
' To detect redundant calls
Private _disposedValue As Boolean
' Instantiate a SafeHandle instance.
Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
_safeHandle?.Dispose()
_safeHandle = Nothing
End If
_disposedValue = True
End If
' Call base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class
메모
이전 예제에서는 SafeFileHandle 개체를 사용하여 패턴을 보여 줍니다. SafeHandle 파생된 모든 개체를 대신 사용할 수 있습니다. 이 예제에서는 SafeFileHandle 개체를 제대로 인스턴스화하지 않습니다.
다음은 Object.Finalize재정의하는 파생 클래스에 대한 dispose 패턴을 구현하는 일반적인 패턴입니다.
public class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
// To detect redundant calls
private bool _disposedValue;
~DerivedClassWithFinalizer() => Dispose(false);
// Protected implementation of Dispose pattern.
protected override void Dispose(bool disposing)
{
if (!_disposedValue)
{
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.
_disposedValue = true;
}
// Call the base class implementation.
base.Dispose(disposing);
}
}
Public Class DerivedClassWithFinalizer
Inherits BaseClassWithFinalizer
' To detect redundant calls
Private _disposedValue As Boolean
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
' Protected implementation of Dispose pattern.
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
End If
' TODO free unmanaged resources (unmanaged objects) And override a finalizer below.
' TODO: set large fields to null.
_disposedValue = True
End If
' Call the base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class
참고 항목
.NET