Реализация метода DisposeImplement a Dispose method

Реализация метода Dispose в основном используется для освобождения неуправляемых ресурсов, которые используются в коде.Implementing the Dispose method is primarily for releasing unmanaged resources used by your code. При работе с членами экземпляра, которые являются реализациями 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, such as undoing something that was previously done. Так можно освободить выделенную память, удалить из коллекции добавленный элемент, сообщить об отмене запрошенной блокировки и т. д.For example, freeing memory that was allocated, removing an item from a collection that was added, signaling the release of a lock that was acquired, and so on.

Сборщик мусора .NET не выделяет и не освобождает неуправляемую память.The .NET garbage collector does not allocate or release unmanaged memory. Шаблон освобождения объекта налагает определенные правила на время существования объекта.The pattern for disposing an object, referred to as the dispose pattern, imposes order on the lifetime of an object. Шаблон освобождения используется для объектов, которые реализуют интерфейс 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 должен быть идемпотентным, то есть поддерживать многократный вызов без создания исключений.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.

Безопасные дескрипторыSafe 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. Например, класс SafeFileHandle является производным от SafeHandle, выступает оболочкой для структур IntPtrs, которые определяют открытые дескрипторы файлов, а также переопределяет свой метод SafeHandle.ReleaseHandle() для его закрытия (через функцию close в UNIX или CloseHandle в Windows).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). Большинство API-интерфейсов в библиотеках .NET, которые создают неуправляемый ресурс, заключают его в 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: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) метод IDisposable.Dispose с атрибутом NonInheritable и без параметров;A public non-virtual (NonInheritable in Visual Basic) IDisposable.Dispose implementation that has no parameters.

  • метод Dispose с атрибутом protected virtual (Overridable в Visual Basic) со следующей сигнатурой: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) метод Dispose с атрибутом NonInheritable без параметров вызывается объектом-получателем типа, его назначение состоит в том, чтобы освободить неуправляемые ресурсы и указать, что метод завершения, если он задан, не должен выполняться.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(Boolean)The Dispose(bool) method overload

В этой перегрузке параметр disposing типа Boolean указывает, откуда осуществляется вызов метода: из метода Dispose (значение true) или из метода завершения (значение false).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 (каскадное удаление).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.

Если метод вызывается из метода завершения, должен выполняться только тот код, который освобождает неуправляемые ресурсы.If the method call comes from a finalizer, only the code that frees unmanaged resources should execute. Разработчик обязан следить, чтобы эта ветвь кода не взаимодействовала с управляемыми объектами, которые могли быть освобождены.The implementer is responsible for ensuring the 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.

Каскадные вызовы DisposeCascade 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 (или NotInheritable в Visual Basic).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

Реализация шаблона освобожденияImplement the dispose pattern

Все незапечатанные классы (в Visual Basic это классы, не имеющие модификатора NotInheritable) должны считаться потенциальным базовым классом, так как они поддерживают наследование.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.

Важно!

Базовый класс может ссылаться только на управляемые объекты и реализовывать шаблон освобождения.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.

Вот общий шаблон реализации шаблона удаления для базового класса, который использует безопасный дескриптор.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().

Реализация шаблона освобождения для производного класса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) базового класса (MyBase.Dispose(bool) в Visual Basic) и передавать состояние освобождения ресурса в качестве аргумента.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. Если есть метод завершения, он должен вызывать перегрузку Dispose(bool), указывая аргумент disposing со значением false.If you do provide a finalizer, it must call the Dispose(bool) overload with a disposing argument of false.

Вот общий шаблон реализации шаблона удаления для производного класса, который использует безопасный дескриптор: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

Реализация шаблона освобождения с безопасными дескрипторамиImplement the dispose pattern with safe handles

В следующем примере показан шаблон удаления для базового класса 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

Реализация шаблона освобождения для производного класса с безопасными дескрипторами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