Dispose 메서드 구현Implement a Dispose method

Dispose 메서드 구현은 주로 관리되지 않는 리소스를 해제하는 데 사용됩니다.Implementing the Dispose method is primarily for releasing unmanaged resources. IDisposable 구현인 인스턴스 멤버를 사용하는 경우에는 Dispose 호출을 계단식 배열하는 것이 일반적입니다.When working with instance members that are IDisposable implementations, it's common to cascade Dispose calls. 예를 들어 할당된 메모리를 해제하거나, 컬렉션에 추가된 항목을 제거하거나, 획득한 잠금 해제를 알리는 등 Dispose를 구현하는 추가적인 이유가 있습니다.There are additional reasons for implementing Dispose, for example, to free memory that was allocated, remove an item that was added to a collection, or signal the release of a lock that was acquired.

.NET 가비지 수집기는 관리되지 않는 메모리를 할당하거나 해제하지 않습니다.The .NET garbage collector does not allocate or release unmanaged memory. Dispose 패턴이라고도 하는 개체 삭제 패턴에서는 개체의 수명에 순서를 적용합니다.The pattern for disposing an object, referred to as the dispose pattern, imposes order on the lifetime of an object. Dispose 패턴은 IDisposable 인터페이스를 구현하는 개체에 사용되며, 파일 및 파이프 핸들, 레지스트리 핸들, 대기 핸들 또는 관리되지 않는 메모리 블록에 대한 포인터와 상호 작용하는 경우 일반적으로 사용됩니다.The dispose pattern is used for objects that implement the IDisposable interface, and is common when interacting with file and pipe handles, registry handles, wait handles, or pointers to blocks of unmanaged memory. 이는 가비지 수집기가 관리되지 않는 개체를 회수할 수 없기 때문입니다.This is because the garbage collector is unable to reclaim unmanaged objects.

리소스가 항상 적절하게 정리되게 하려면 Dispose 메서드가 멱등원(idempotent)이어야 합니다(예외를 throw하지 않고 여러 번 호출할 수 있음).To help ensure that resources are always cleaned up appropriately, a Dispose method should be idempotent, such that it is callable multiple times without throwing an exception. 또한 Dispose의 후속 호출은 아무 작업도 수행하지 않아야 합니다.Furthermore, subsequent invocations of Dispose should do nothing.

GC.KeepAlive 메서드에 대해 제공된 코드 예에서는 개체 또는 개체의 멤버에 대한 관리되지 않는 참조가 여전히 사용 중인 동안 가비지 수집으로 인해 종료자가 실행되게 할 수 있는 방법을 보여 줍니다.The code example provided for the GC.KeepAlive method shows how garbage collection can cause a finalizer to run, while an unmanaged reference to the object or its members is still in use. GC.KeepAlive를 활용하여 현재 루틴의 시작부터 이 메서드가 호출되는 시점까지 개체를 가비지 수집에 부적절하도록 만드는 것이 좋을 수도 있습니다.It may make sense to utilize GC.KeepAlive to make the object ineligible for garbage collection from the start of the current routine to the point where this method is called.

SafeHandleSafe handles

개체 종료자에 대한 코드를 작성하는 작업은 올바르게 수행되지 않을 경우 문제를 일으킬 수 있는 복잡한 작업입니다.Writing code for an object's finalizer is a complex task that can cause problems if not done correctly. 따라서 종료자를 구현하는 대신 System.Runtime.InteropServices.SafeHandle 개체를 생성하는 것이 좋습니다.Therefore, we recommend that you construct System.Runtime.InteropServices.SafeHandle objects instead of implementing a finalizer.

System.Runtime.InteropServices.SafeHandle은 관리되지 않는 리소스를 식별하는 System.IntPtr을 래핑하는 관리되는 추상 형식입니다.A System.Runtime.InteropServices.SafeHandle is an abstract managed type that wraps an System.IntPtr that identifies an unmanaged resource. Windows에서는 핸들을 식별하며, Unix에서는 파일 설명자를 식별합니다.On Windows it might identify a handle while on Unix, a file descriptor. 이 리소스는 SafeHandle이 삭제되거나 SafeHandle에 대한 모든 참조가 삭제되고 SafeHandle 인스턴스가 종료될 때 한 번만 해제되도록 하는 데 필요한 모든 논리를 제공합니다.It provides all of the logic necessary to ensure that this resource is released once and only once, when the SafeHandle is disposed of or when all references to the SafeHandle have been dropped and the SafeHandle instance is finalized.

System.Runtime.InteropServices.SafeHandle은 추상 기본 클래스입니다.The System.Runtime.InteropServices.SafeHandle is an abstract base class. 파생 클래스는 다양한 종류의 핸들에 대해 특정 인스턴스를 제공합니다.Derived classes provide specific instances for different kinds of handle. 이러한 파생 클래스는 잘못된 것으로 간주되는 System.IntPtr 값과 실제로 핸들을 해제하는 방법의 유효성을 검사합니다.These derived classes validate what values for the System.IntPtr are considered invalid and how to actually free the handle. 예를 들어 SafeFileHandleSafeHandle에서 파생되어 열려 있는 파일 핸들/설명자를 식별하는 IntPtrs을 래핑하고 SafeHandle.ReleaseHandle() 메서드를 재정의하여 닫습니다(Unix의 close 함수 또는 Windows의 CloseHandle 함수를 통해).For example, SafeFileHandle derives from SafeHandle to wrap IntPtrs that identify open file handles/descriptors, and overrides its SafeHandle.ReleaseHandle() method to close it (via the close function on Unix or CloseHandle function on Windows). 관리되지 않는 리소스를 만드는 대부분의 .NET 라이브러리 API는 원시 포인터를 다시 전달하는 대신 SafeHandle에서 이를 래핑하고 필요에 따라 해당 SafeHandle을 사용자에게 반환합니다.Most APIs in .NET libraries that create an unmanaged resource will wrap it in a SafeHandle and return that SafeHandle to you as needed, rather than handing back the raw pointer. 관리되지 않는 구성 요소와 상호 작용하고 관리되지 않는 리소스에 대한 IntPtr을 가져오는 경우 고유한 SafeHandle 형식을 만들어 래핑할 수 있습니다.In situations where you interact with an unmanaged component and get an IntPtr for an unmanaged resource, you can create your own SafeHandle type to wrap it. 따라서 비 SafeHandle 형식이 종료자를 구현해야 하는 경우는 거의 없습니다. 대부분의 삭제 가능한 패턴 구현에서는 다른 관리되는 리소스(이 중 일부는 SafeHandle이 될 수 있음)를 래핑하는 것으로 끝납니다.As a result, few non-SafeHandle types need to implement finalizers; most disposable pattern implementations only end up wrapping other managed resources, some of which may be SafeHandles.

Microsoft.Win32.SafeHandles 네임스페이스에서 다음과 같은 파생된 클래스가 SafeHandle을 제공합니다.The following derived classes in the Microsoft.Win32.SafeHandles namespace provide safe handles:

Dispose() 및 Dispose(bool)Dispose() and Dispose(bool)

IDisposable 인터페이스에서는 매개 변수가 없는 단일 메서드인 Dispose를 구현해야 합니다.The IDisposable interface requires the implementation of a single parameterless method, Dispose. 또한 모든 봉인되지 않은 클래스에는 추가 Dispose(bool) 오버로드 메서드가 구현되어야 합니다.Also, any non-sealed class should have an additional Dispose(bool) overload method to be implemented:

  • 매개 변수가 없는 public 비가상(Visual Basic의 경우 NonInheritable) IDisposable.Dispose 구현A public non-virtual (NonInheritable in Visual Basic) IDisposable.Dispose implementation that has no parameters.

  • 서명이 다음과 같은 protected virtual(Visual Basic의 경우 Overridable) Dispose 메서드A protected virtual (Overridable in Visual Basic) Dispose method whose signature is:

    protected virtual void Dispose(bool disposing)
    {
    }
    
    Protected Overridable Sub Dispose(disposing As Boolean)
    End Sub
    

    중요

    disposing 매개 변수가 종료자에서 호출되는 경우 false이고 IDisposable.Dispose 메서드에서 호출되는 경우 true여야 합니다.The disposing parameter should be false when called from a finalizer, and true when called from the IDisposable.Dispose method. 즉, 결정적으로 호출되는 경우에는 true이고, 비결정적으로 호출되는 경우에는 false입니다.In other words, it is true when deterministically called and false when non-deterministically called.

Dispose() 메서드The Dispose() method

public, 비가상(Visual Basic의 경우 NonInheritable), 매개 변수 없는 Dispose 메서드는 형식의 소비자에 의해 호출되므로, 관리되지 않는 리소스를 해제하고, 일반 정리를 수행하고, 종료자(있는 경우)를 실행할 필요가 없음을 나타내기 위한 것입니다.Because the public, non-virtual (NonInheritable in Visual Basic), parameterless Dispose method is called by a consumer of the type, its purpose is to free unmanaged resources, perform general cleanup, and to indicate that the finalizer, if one is present, doesn't have to run. 관리되는 개체와 관련된 실제 메모리를 해제하는 것은 항상 가비지 수집기의 영역입니다.Freeing the actual memory associated with a managed object is always the domain of the garbage collector. 이로 인해 다음과 같은 표준 구현이 수행됩니다.Because of this, it has a standard implementation:

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 재정의를 호출할 필요가 없습니다.The Dispose method performs all object cleanup, so the garbage collector no longer needs to call the objects' Object.Finalize override. 따라서 SuppressFinalize 메서드를 호출하면 가비지 수집기가 종료자를 실행하지 않도록 방지됩니다.Therefore, the call to the SuppressFinalize method prevents the garbage collector from running the finalizer. 형식에 종료자가 없는 경우 GC.SuppressFinalize에 대한 호출이 영향을 미치지 않습니다.If the type has no finalizer, the call to GC.SuppressFinalize has no effect. 실제 정리는 Dispose(bool) 메서드 오버로드에 의해 수행됩니다.Note that the actual cleanup is performed by the Dispose(bool) method overload.

Dispose(bool) 메서드 오버로드The Dispose(bool) method overload

오버로드에서 disposing 매개 변수는 메서드 호출이 Dispose 메서드(값이 true)에서 수행되는지 또는 종료자(값이 false)에서 수행되는지를 나타내는 Boolean입니다.In the overload, the disposing parameter is a Boolean that indicates whether the method call comes from a Dispose method (its value is true) or from a finalizer (its value is false).

메서드 본문은 다음과 같은 두 가지 코드 블록으로 구성됩니다.The body of the method consists of two blocks of code:

  • 관리되지 않는 리소스를 해제하는 블록.A block that frees unmanaged resources. 이 블록은 disposing 매개 변수의 값에 관계없이 실행됩니다.This block executes regardless of the value of the disposing parameter.

  • 관리되는 리소스를 해제하는 조건부 블록.A conditional block that frees managed resources. 이 블록은 disposing 값이 true인 경우 실행됩니다.This block executes if the value of disposing is true. 해제되는 관리되는 리소스는 다음과 같습니다.The managed resources that it frees can include:

    • IDisposable을 구현하는 관리되는 개체.Managed objects that implement IDisposable. 조건부 블록을 사용하여 Dispose 구현(cascade dispose)을 호출할 수 있습니다.The conditional block can be used to call their Dispose implementation (cascade dispose). 관리되지 않는 리소스를 래핑하기 위해 System.Runtime.InteropServices.SafeHandle의 파생 클래스를 사용한 경우 여기에서 SafeHandle.Dispose() 구현을 호출해야 합니다.If you have used a derived class of System.Runtime.InteropServices.SafeHandle to wrap your unmanaged resource, you should call the SafeHandle.Dispose() implementation here.

    • 많은 메모리를 사용하거나 부족한 리소스를 사용하는 관리되는 개체.Managed objects that consume large amounts of memory or consume scarce resources. 관리되는 큰 개체 참조를 null에 할당하여 더 연결할 수 없는 상태로 만듭니다.Assign large managed object references to null to make them more likely to be unreachable. 이렇게 하면 비결정적으로 회수된 경우보다 빠르게 해제되며, 이러한 과정은 일반적으로 조건부 블록 외부에서 이루어집니다.This releases them faster than if they were reclaimed non-deterministically, and this is usually done outside of the conditional block.

메서드 호출이 종료자에서 수행된 경우 관리되지 않는 리소스를 해제하는 코드만 실행되어야 합니다.If the method call comes from a finalizer, only the code that frees unmanaged resources should execute. 구현자는 false 경로가 회수되었을 수 있는 관리 개체와 상호 작용하지 않도록 해야 합니다.The implementer is responsible for ensuring that the false path doesn't interact with managed objects that may have been reclaimed. 이는 종료하는 동안 가비지 수집기가 관리되는 개체를 제거하는 순서가 비결정적이기 때문에 중요합니다.This is important because the order in which the garbage collector destroys managed objects during finalization is non-deterministic.

Cascade dispose 호출Cascade dispose calls

클래스가 필드 또는 속성을 소유하고 해당 형식이 IDisposable을 구현하는 경우 포함하는 클래스 자체도 IDisposable을 구현해야 합니다.If your class owns a field or property, and its type implements IDisposable, the containing class itself should also implement IDisposable. IDisposable 구현을 인스턴스화하고 인스턴스 멤버로 저장하는 클래스도 정리를 담당합니다.A class that instantiates an IDisposable implementation and storing it as an instance member, is also responsible for its cleanup. 이는 참조되는 삭제 가능한 형식에 Dispose 메서드를 통해 결정적으로 정리를 수행할 수 있는 기회를 제공하기 위한 것입니다.This is to help ensure that the referenced disposable types are given the opportunity to deterministically perform clean up through the Dispose method. 이 예제에서 클래스는 sealed입니다(Visual Basic의 경우 NotInheritable).In this example, the class is sealed (or NotInheritable in Visual Basic).

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

Dispose 패턴 구현Implement the dispose pattern

모든 봉인되지 않은 클래스 또는 NotInheritable으로 수정되지 않는 Visual Basic 클래스는 상속될 수 있기 때문에 잠재적 기본 클래스로 간주해야 합니다.All non-sealed classes or (Visual Basic classes not modified as NotInheritable) should be considered a potential base class, because they could be inherited. 잠재적 기본 클래스에 대한 삭제 패턴을 구현하는 경우 다음을 제공해야 합니다.If you implement the dispose pattern for any potential base class, you must provide the following:

  • Dispose 메서드를 호출하는 Dispose(bool) 구현A Dispose implementation that calls the Dispose(bool) method.
  • 실제 정리를 수행하는 Dispose(bool) 메서드A Dispose(bool) method that performs the actual cleanup.
  • 관리되지 않는 리소스를 래핑하는 SafeHandle에서 파생된 클래스(권장) 또는 Object.Finalize 메서드 재정의Either a class derived from SafeHandle that wraps your unmanaged resource (recommended), or an override to the Object.Finalize method. SafeHandle 클래스는 종료자를 제공하므로 직접 작성할 필요가 없습니다.The SafeHandle class provides a finalizer, so you do not have to write one yourself.

중요

기본 클래스는 관리되는 개체만 참조할 수 있으며 Dispose 패턴을 구현할 수 있습니다.It is possible for a base class to only reference managed objects, and implement the dispose pattern. 이러한 경우 종료자는 필요하지 않습니다.In these cases, a finalizer is unnecessary. 종료자는 관리되지 않는 리소스를 직접 참조하는 경우에만 필요합니다.A finalizer is only required if you directly reference unmanaged resources.

SafeHandle을 사용하는 기본 클래스에 대한 삭제 패턴을 구현하는 일반적인 패턴은 다음과 같습니다.Here's the general pattern for implementing the dispose pattern for a base class that uses a safe handle.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class BaseClass : IDisposable
{
    // To detect redundant calls
    private bool _disposed = false;

    // 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 (_disposed)
        {
            return;
        }

        if (disposing)
        {
           // Dispose managed state (managed objects).
            _safeHandle?.Dispose();
        }

        _disposed = true;
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class BaseClass : 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()
            ' Free any other managed objects here.
            '
        End If

        disposed = True
    End Sub
End Class

참고

이전 예제에서는 SafeFileHandle 개체를 사용하여 패턴을 보여 줍니다. SafeHandle에서 파생된 개체를 대신 사용할 수 있습니다.The previous example uses a SafeFileHandle object to illustrate the pattern; any object derived from SafeHandle could be used instead. 예제에서는 해당 SafeFileHandle 개체를 제대로 인스턴스화하지 않습니다.Note that the example does not properly instantiate its SafeFileHandle object.

Object.Finalize를 재정의하는 기본 클래스에 대한 삭제 패턴을 구현하는 일반적인 패턴은 다음과 같습니다.Here's the general pattern for implementing the dispose pattern for a base class that overrides Object.Finalize.

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;
    }
}
Class BaseClass : 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
            ' Free any other managed objects here.
            '
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub
End Class

C#에서는 Object.Finalize를 재정의하여 종료자를 만듭니다.In C#, you create a finalizer by overriding Object.Finalize. Visual Basic에서는 Protected Overrides Sub Finalize()를 사용하여 이 작업을 수행합니다.In Visual Basic, this is done with Protected Overrides Sub Finalize().

파생 클래스에 대한 Dispose 패턴 구현Implement the dispose pattern for a derived class

IDisposable의 기본 클래스 구현은 파생된 클래스에 의해 상속되므로 IDisposable 인터페이스를 구현하는 클래스에서 파생된 클래스가 IDisposable.Dispose을 구현하지 않아야 합니다.A class derived from a class that implements the IDisposable interface shouldn't implement IDisposable, because the base class implementation of IDisposable.Dispose is inherited by its derived classes. 대신, 파생 클래스를 정리하려면 다음을 제공합니다.Instead, to cleanup a derived class, you provide the following:

  • 기본 클래스 메서드를 재정의하고 파생 클래스의 실제 정리를 수행하는 protected override void Dispose(bool) 메서드.A protected override void Dispose(bool) method that overrides the base class method and performs the actual cleanup of the derived class. 또한 이 메서드는 기본 클래스의 base.Dispose(bool)(Visual Basic의 경우 MyBase.Dispose(bool)) 메서드를 호출하며 인수에 대해 삭제 중 상태를 전달합니다.This method must also call the base.Dispose(bool) (MyBase.Dispose(bool) in Visual Basic) method of the base class and pass its disposing status for the argument.
  • 관리되지 않는 리소스를 래핑하는 SafeHandle에서 파생된 클래스(권장) 또는 Object.Finalize 메서드 재정의Either a class derived from SafeHandle that wraps your unmanaged resource (recommended), or an override to the Object.Finalize method. SafeHandle 클래스는 사용자가 코딩할 필요가 없는 종료자를 제공합니다.The SafeHandle class provides a finalizer that frees you from having to code one. 종료자를 제공하는 경우 disposing 인수가 falseDispose(bool) 오버로드를 호출해야 합니다.If you do provide a finalizer, it must call the Dispose(bool) overload with a disposing argument of false.

SafeHandle을 사용하는 파생된 클래스에 대한 삭제 패턴을 구현하는 일반적인 패턴은 다음과 같습니다.Here's the general pattern for implementing the dispose pattern for a derived class that uses a safe handle:

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class DerivedClass : BaseClass
{
    // To detect redundant calls
    private bool _disposed = false;

    // Instantiate a SafeHandle instance.
    private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        if (disposing)
        {
           // Dispose managed state (managed objects).
            _safeHandle?.Dispose();
        }

        _disposed = true;

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class DerivedClass : Inherits BaseClass
    ' 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에서 파생된 개체를 대신 사용할 수 있습니다.The previous example uses a SafeFileHandle object to illustrate the pattern; any object derived from SafeHandle could be used instead. 예제에서는 해당 SafeFileHandle 개체를 제대로 인스턴스화하지 않습니다.Note that the example does not properly instantiate its SafeFileHandle object.

Object.Finalize를 재정의하는 파생된 클래스에 대한 삭제 패턴을 구현하는 일반적인 패턴은 다음과 같습니다.Here's the general pattern for implementing the dispose pattern for a derived class that overrides Object.Finalize:

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);
    }
}
Class DerivedClass : Inherits BaseClass
    ' 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
            ' Free any other managed objects here.
            '
        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

SafeHandle을 사용하여 Dispose 패턴 구현Implement the dispose pattern with safe handles

다음 예제는 SafeHandle을 사용하여 관리되지 않는 리소스를 캡슐화하는 기본 클래스에 대한 삭제 패턴인 DisposableStreamResource를 보여 줍니다.The following example illustrates the dispose pattern for a base class, DisposableStreamResource, that uses a safe handle to encapsulate unmanaged resources. 이는 DisposableStreamResource을 사용하여 열려 있는 파일을 나타내는 SafeFileHandle 개체를 래핑하는 Stream 클래스를 정의합니다.It defines a DisposableStreamResource class that uses a SafeFileHandle to wrap a Stream object that represents an open file. 또한 이 클래스에는 파일 스트림에서 총 바이트 수를 반환하는 단일 속성인 Size도 포함됩니다.The class also includes a single property, Size, that returns the total number of bytes in the file stream.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class DisposableStreamResource : IDisposable
{
    // Define constants.
    protected const uint GENERIC_READ = 0x80000000;
    protected const uint FILE_SHARE_READ = 0x00000001;
    protected const uint OPEN_EXISTING = 3;
    protected const uint FILE_ATTRIBUTE_NORMAL = 0x80;
    private const int INVALID_FILE_SIZE = unchecked((int)0xFFFFFFFF);

    // Define Windows APIs.
    [DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode)]
    protected static extern SafeFileHandle CreateFile(
        string lpFileName, uint dwDesiredAccess,
        uint dwShareMode, IntPtr lpSecurityAttributes,
        uint dwCreationDisposition, uint dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("kernel32.dll")]
    private static extern int GetFileSize(
        SafeFileHandle hFile, out int lpFileSizeHigh);

    // Define locals.
    private bool _disposed = false;
    private readonly SafeFileHandle _safeHandle;
    private readonly int _upperWord;

    public DisposableStreamResource(string fileName)
    {
        if (string.IsNullOrWhiteSpace(fileName))
        {
            throw new ArgumentException("The fileName cannot be null or an empty string");
        }

        _safeHandle = CreateFile(
            fileName, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);

        // Get file size.
        Size = GetFileSize(_safeHandle, out _upperWord);
        if (Size == INVALID_FILE_SIZE)
        {
            Size = -1;
        }
        else if (_upperWord > 0)
        {
            Size = (((long)_upperWord) << 32) + Size;
        }
    }

    public long Size { get; }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        // Dispose of managed resources here.
        if (disposing)
        {
            _safeHandle?.Dispose();
        }

        // Dispose of any unmanaged resources not wrapped in safe handles.

        _disposed = true;
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.IO

Public Class DisposableStreamResource : Implements IDisposable
    ' Define constants.
    Protected Const GENERIC_READ As UInteger = &H80000000UI
    Protected Const FILE_SHARE_READ As UInteger = &H0I
    Protected Const OPEN_EXISTING As UInteger = 3
    Protected Const FILE_ATTRIBUTE_NORMAL As UInteger = &H80
    Private Const INVALID_FILE_SIZE As Integer = &HFFFFFFFF

    ' Define Windows APIs.
    Protected Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (
        lpFileName As String, dwDesiredAccess As UInt32,
        dwShareMode As UInt32, lpSecurityAttributes As IntPtr,
        dwCreationDisposition As UInt32, dwFlagsAndAttributes As UInt32,
        hTemplateFile As IntPtr) As SafeFileHandle

    Private Declare Function GetFileSize Lib "kernel32" (
        hFile As SafeFileHandle, ByRef lpFileSizeHigh As Integer) As Integer

    ' Define locals.
    Private disposed As Boolean = False
    Private ReadOnly safeHandle As SafeFileHandle
    Private ReadOnly upperWord As Integer

    Public Sub New(fileName As String)
        If String.IsNullOrWhiteSpace(fileName) Then
            Throw New ArgumentNullException("The fileName cannot be null or an empty string")
        End If

        safeHandle = CreateFile(
            fileName, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero)

        ' Get file size.
        Size = GetFileSize(safeHandle, upperWord)
        If Size = INVALID_FILE_SIZE Then
            Size = -1
        ElseIf upperWord > 0 Then
            Size = (CLng(upperWord) << 32) + Size
        End If
    End Sub

    Public ReadOnly Property Size As Long

    Public Sub Dispose() _
              Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    Protected Overridable Sub Dispose(disposing As Boolean)
        If disposed Then Exit Sub

        ' Dispose of managed resources here.
        If disposing Then
            safeHandle.Dispose()
        End If

        ' Dispose of any unmanaged resources not wrapped in safe handles.

        disposed = True
    End Sub
End Class

SafeHandle을 사용하여 파생 클래스에 대한 Dispose 패턴 구현Implement the dispose pattern for a derived class with safe handles

다음 예제는 이전 예제에 제공된 DisposableStreamResource2 클래스에서 상속되는 파생된 클래스에 대한 삭제 패턴인 DisposableStreamResource를 보여 줍니다.The following example illustrates the dispose pattern for a derived class, DisposableStreamResource2, that inherits from the DisposableStreamResource class presented in the previous example. 클래스에서 추가 메서드인 WriteFileInfo를 추가하고 SafeFileHandle 개체를 사용하여 쓰기 가능 파일의 핸들을 래핑합니다.The class adds an additional method, WriteFileInfo, and uses a SafeFileHandle object to wrap the handle of the writable file.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class DisposableStreamResource2 : DisposableStreamResource
{
    // Define additional constants.
    protected const uint GENERIC_WRITE = 0x40000000;
    protected const uint OPEN_ALWAYS = 4;

    // Define additional APIs.
    [DllImport("kernel32.dll")]
    protected static extern bool WriteFile(
        SafeFileHandle safeHandle, string lpBuffer,
        int nNumberOfBytesToWrite, out int lpNumberOfBytesWritten,
        IntPtr lpOverlapped);

    // To detect redundant calls
    private bool _disposed = false;
    private bool _created = false;
    private SafeFileHandle _safeHandle;
    private readonly string _fileName;

    public DisposableStreamResource2(string fileName) : base(fileName) => _fileName = fileName;

    public void WriteFileInfo()
    {
        if (!_created)
        {
            _safeHandle = CreateFile(
                @".\FileInfo.txt", GENERIC_WRITE, 0, IntPtr.Zero,
                OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);

            _created = true;
        }

        string output = $"{_fileName}: {Size:N0} bytes\n";
        _ = WriteFile(_safeHandle, output, output.Length, out _, IntPtr.Zero);
    }

    protected override void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        // Release any managed resources here.
        if (disposing)
        {
            // Dispose managed state (managed objects).
            _safeHandle?.Dispose();
        }

        // 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);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.IO

Public Class DisposableStreamResource2 : Inherits DisposableStreamResource
    ' Define additional constants.
    Protected Const GENERIC_WRITE As Integer = &H40000000
    Protected Const OPEN_ALWAYS As Integer = 4

    ' Define additional APIs.
    Protected Declare Function WriteFile Lib "kernel32.dll" (
        safeHandle As SafeFileHandle, lpBuffer As String,
        nNumberOfBytesToWrite As Integer, ByRef lpNumberOfBytesWritten As Integer,
        lpOverlapped As Object) As Boolean

    ' Define locals.
    Private disposed As Boolean = False
    Private created As Boolean = False
    Private safeHandle As SafeFileHandle
    Private ReadOnly filename As String

    Public Sub New(filename As String)
        MyBase.New(filename)
        Me.filename = filename
    End Sub

    Public Sub WriteFileInfo()
        If Not created Then
            safeHandle = CreateFile(
                ".\FileInfo.txt", GENERIC_WRITE, 0, IntPtr.Zero,
                OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero)
            created = True
        End If

        Dim output As String = $"{filename }: {Size:N0} bytes {vbCrLf }"
        Dim result = WriteFile(safeHandle, output, output.Length, 0&, Nothing)
    End Sub

    Protected Overridable Overloads Sub Dispose(disposing As Boolean)
        If disposed Then Exit Sub

        ' Release any managed resources here.
        If disposing Then
            safeHandle?.Dispose()
        End If

        disposed = True
        ' Release any unmanaged resources not wrapped by safe handles here.

        ' Call the base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

참조See also