Dispose 메서드 구현
메서드는 Dispose 주로 관리되지 않는 리소스를 해제하기 위해 구현됩니다. IDisposable 구현인 인스턴스 멤버를 사용하는 경우에는 Dispose 호출을 계단식 배열하는 것이 일반적입니다. 예를 들어 할당된 메모리를 해제하거나, 컬렉션에 추가된 항목을 제거하거나, 획득한 잠금 해제를 알리는 등 Dispose를 구현하는 추가적인 이유가 있습니다.
.NET 가비지 수집기는 관리되지 않는 메모리를 할당하거나 해제하지 않습니다. Dispose 패턴이라고도 하는 개체 삭제 패턴에서는 개체의 수명에 순서를 적용합니다. 삭제 패턴은 인터페이스를 구현하는 개체에 IDisposable 사용됩니다. 이 패턴은 가비지 수집기가 관리되지 않는 개체를 회수할 수 없기 때문에 파일 및 파이프 핸들, 레지스트리 핸들, 대기 핸들 또는 비관리형 메모리 블록에 대한 포인터와 상호 작용할 때 일반적입니다.
리소스가 항상 적절하게 정리되게 하려면 Dispose 메서드가 멱등원(idempotent)이어야 합니다(예외를 throw하지 않고 여러 번 호출할 수 있음). 또한 Dispose의 후속 호출은 아무 작업도 수행하지 않아야 합니다.
메서드에 제공된 코드 예제에서는 개체 또는 해당 멤버에 대한 GC.KeepAlive 관리되지 않는 참조가 계속 사용 중인 동안 가비지 수집으로 인해 종료자가 실행되는 방법을 보여 주며, GC.KeepAlive를 활용하여 현재 루틴의 시작부터 이 메서드가 호출되는 시점까지 개체를 가비지 수집에 부적절하도록 만드는 것이 좋을 수도 있습니다.
팁
종속성 주입과 관련하여 에 서비스를 등록할 때 서비스 IServiceCollection수명은 사용자를 대신하여 암시적으로 관리됩니다. IServiceProvider 및 해당 IHost 오케스트레이션 리소스 정리. 특히 및 IAsyncDisposable 의 IDisposable 구현은 지정된 수명이 끝날 때 올바르게 삭제됩니다.
자세한 내용은 .NET에서 종속성 주입을 참조하세요.
SafeHandle
개체 종료자에 대한 코드를 작성하는 작업은 올바르게 수행되지 않을 경우 문제를 일으킬 수 있는 복잡한 작업입니다. 따라서 종료자를 구현하는 대신 System.Runtime.InteropServices.SafeHandle 개체를 생성하는 것이 좋습니다.
System.Runtime.InteropServices.SafeHandle은 관리되지 않는 리소스를 식별하는 System.IntPtr을 래핑하는 관리되는 추상 형식입니다. Windows에서는 핸들을 식별하고 Unix에서 파일 설명자를 식별할 수 있습니다. 는 SafeHandle
가 삭제되거나 에 대한 모든 참조 SafeHandle
가 삭제되고 instance 완료될 때 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
(Overridable
Visual Basic의 경우)Dispose(bool)
.
Dispose() 메서드
public
Visual Basic에서는 가상NotOverridable
이 아닌 메서드가 더 이상 필요하지 않을 때 매개 변수 없는 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)
{
// 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;
}
Protected Overridable Sub Dispose(disposing As Boolean)
If disposed Then Exit Sub
' A block that frees unmanaged resources.
If disposing Then
' Deterministic call…
' A conditional block that frees managed resources.
End If
disposed = True
End Sub
중요
disposing
매개 변수가 종료자에서 호출되는 경우 false
이고 IDisposable.Dispose 메서드에서 호출되는 경우 true
여야 합니다. 즉, 결정적으로 호출되는 경우에는 true
이고, 비결정적으로 호출되는 경우에는 false
입니다.
메서드의 본문은 다음 세 가지 코드 블록으로 구성됩니다.
개체가 이미 삭제된 경우 조건부 반환을 위한 블록입니다.
관리되지 않는 리소스를 해제하는 블록. 이 블록은
disposing
매개 변수의 값에 관계없이 실행됩니다.관리되는 리소스를 해제하는 조건부 블록. 이 블록은
disposing
값이true
인 경우 실행됩니다. 해제되는 관리되는 리소스는 다음과 같습니다.IDisposable을 구현하는 관리되는 개체. 조건부 블록을 사용하여 Dispose 구현(cascade dispose)을 호출할 수 있습니다. 관리되지 않는 리소스를 래핑하기 위해 System.Runtime.InteropServices.SafeHandle의 파생 클래스를 사용한 경우 여기에서 SafeHandle.Dispose() 구현을 호출해야 합니다.
많은 메모리를 사용하거나 부족한 리소스를 사용하는 관리되는 개체 관리되는 큰 개체 참조를
null
에 할당하여 더 연결할 수 없는 상태로 만듭니다. 이렇게 하면 비결정적으로 회수된 경우보다 빠르게 해제됩니다.
메서드 호출이 종료자에서 수행된 경우 관리되지 않는 리소스를 해제하는 코드만 실행되어야 합니다. 구현자는 잘못된 경로가 삭제되었을 수 있는 관리되는 개체와 상호 작용하지 않도록 해야 합니다. 가비지 수집기가 종료 중에 관리되는 개체를 삭제하는 순서는 비결정적이기 때문에 중요합니다.
Cascade dispose 호출
클래스가 필드 또는 속성을 소유하고 해당 형식이 를 구현 IDisposable하는 경우 포함하는 클래스 자체도 를 구현 IDisposable해야 합니다. 구현을 IDisposable 인스턴스화하고 instance 멤버로 저장하는 클래스도 해당 정리를 담당합니다. 이렇게 하면 참조된 삭제 가능한 형식에 메서드를 통해 Dispose 결정적으로 정리를 수행할 수 있는 기회가 제공됩니다. 다음 예제에서 클래스는 (또는 NotInheritable
Visual Basic에서) 입니다 sealed
.
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할 필요가 없습니다.
- 종료자에서 -checking을 수행할
null
수 있는 경우가 있습니다(종료자가 호출한 메서드 포함Dispose(false)
). 주된 이유 중 하나는 instance 완전히 초기화되었는지 확실하지 않은 경우입니다(예: 생성자에서 예외가 throw될 수 있습니다).
Dispose 패턴 구현
봉인되지 않은 모든 클래스(또는 로 수정되지 않은 Visual Basic 클래스)는 상속될 수 있으므로 잠재적인 기본 클래스로 NotInheritable
간주되어야 합니다. 잠재적 기본 클래스에 대한 삭제 패턴을 구현하는 경우 다음을 제공해야 합니다.
- Dispose 메서드를 호출하는
Dispose(bool)
구현 - 실제 정리를 수행하는
Dispose(bool)
메서드 - 관리되지 않는 리소스를 래핑하는 SafeHandle에서 파생된 클래스(권장) 또는 Object.Finalize 메서드 재정의 클래스는 SafeHandle 종료자를 제공하므로 직접 작성할 필요가 없습니다.
중요
기본 클래스는 관리되는 개체만 참조하고 삭제 패턴을 구현할 수 있습니다. 이러한 경우 종료자는 필요하지 않습니다. 종료자는 관리되지 않는 리소스를 직접 참조하는 경우에만 필요합니다.
다음은 안전 핸들을 사용하는 기본 클래스에 대한 삭제 패턴을 구현하기 위한 일반적인 패턴의 예입니다.
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
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);
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_safeHandle.Dispose();
}
_disposedValue = true;
}
}
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices
Class BaseClassWithSafeHandle : Implements IDisposable
' Flag: Has Dispose already been called?
Dim disposed As Boolean = False
' Instantiate a SafeHandle instance.
Dim handle 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(disposing As Boolean)
If disposed Then Return
If disposing Then
handle.Dispose()
End If
disposed = True
End Sub
End Class
참고
이전 예제에서는 SafeFileHandle 개체를 사용하여 패턴을 보여 줍니다. SafeHandle에서 파생된 개체를 대신 사용할 수 있습니다. 예제에서는 해당 SafeFileHandle 개체를 제대로 인스턴스화하지 않습니다.
Object.Finalize를 재정의하는 기본 클래스에 대한 삭제 패턴을 구현하는 일반적인 패턴은 다음과 같습니다.
using System;
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;
}
}
}
Class BaseClassWithFinalizer : Implements IDisposable
' Flag: Has Dispose already been called?
Dim disposed As Boolean = False
' 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(disposing As Boolean)
If disposed Then Return
If disposing Then
' Dispose managed objects that implement IDisposable.
' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
End If
' Free any unmanaged objects here.
'
disposed = True
End Sub
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
End Class
팁
C#에서는 를 재정의하는 것이 아니라 종료자를 제공하여 종료를 구현합니다 Object.Finalize. Visual Basic에서는 를 사용하여 종료자를 Protected Overrides Sub Finalize()
만듭니다.
파생 클래스에 대한 Dispose 패턴 구현
IDisposable의 기본 클래스 구현은 파생된 클래스에 의해 상속되므로 IDisposable 인터페이스를 구현하는 클래스에서 파생된 클래스가 IDisposable.Dispose을 구현하지 않아야 합니다. 대신 파생 클래스를 클린 위해 다음을 제공합니다.
- 기본 클래스 메서드를 재정의하고 파생 클래스의 실제 정리를 수행하는
protected override void Dispose(bool)
메서드. 또한 이 메서드는 삭제 상태base.Dispose(bool)
( 매개 변수)를 인수로 전달하는 (MyBase.Dispose(bool)
bool disposing
Visual Basic의 경우) 메서드를 호출해야 합니다. - 관리되지 않는 리소스를 래핑하는 SafeHandle에서 파생된 클래스(권장) 또는 Object.Finalize 메서드 재정의 SafeHandle 클래스는 사용자가 코딩할 필요가 없는 종료자를 제공합니다. 종료자를 제공하는 경우 인수를 사용하여 오버로드
false
를Dispose(bool)
호출해야 합니다.
다음은 안전 핸들을 사용하는 파생 클래스에 대한 삭제 패턴을 구현하기 위한 일반적인 패턴의 예입니다.
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
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();
}
_disposedValue = true;
}
// Call base class implementation.
base.Dispose(disposing);
}
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices
Class DerivedClassWithSafeHandle : Inherits BaseClassWithSafeHandle
' Flag: Has Dispose already been called?
Dim disposed As Boolean = False
' Instantiate a SafeHandle instance.
Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)
' Protected implementation of Dispose pattern.
Protected Overrides Sub Dispose(disposing As Boolean)
If disposed Then Return
If disposing Then
handle.Dispose()
' Free any other managed objects here.
'
End If
' Free any unmanaged objects here.
'
disposed = True
' Call base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class
참고
이전 예제에서는 SafeFileHandle 개체를 사용하여 패턴을 보여 줍니다. SafeHandle에서 파생된 개체를 대신 사용할 수 있습니다. 예제에서는 해당 SafeFileHandle 개체를 제대로 인스턴스화하지 않습니다.
Object.Finalize를 재정의하는 파생된 클래스에 대한 삭제 패턴을 구현하는 일반적인 패턴은 다음과 같습니다.
class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
// To detect redundant calls
private bool _disposedValue;
~DerivedClassWithFinalizer() => this.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);
}
}
Class DerivedClassWithFinalizer : Inherits BaseClassWithFinalizer
' Flag: Has Dispose already been called?
Dim disposed As Boolean = False
' Protected implementation of Dispose pattern.
Protected Overrides Sub Dispose(disposing As Boolean)
If disposed Then Return
If disposing Then
' Dispose managed objects that implement IDisposable.
' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
End If
' Free any unmanaged objects here.
'
disposed = True
' Call the base class implementation.
MyBase.Dispose(disposing)
End Sub
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
End Class