Реализация методов Finalize и Dispose для очистки неуправляемых ресурсов

ПримечаниеПримечание

Сведения о доработке и утилизации ресурсов с помощью C++, см. Destructors and Finalizers in Visual C++.

Экземпляры класса часто инкапсулируют элементы управления ресурсами, которые не управляются средой выполнения, такие как дескрипторы окон (HWND), подключения к базе данных и т. д. По этой причине следует задавать как явные, так и неявные способы освобождения таких ресурсов. Следует осуществить неявный вызов путем реализации защищенного Finalize для объекта (синтаксис деструктора в C# и C++). Сборщик мусора вызывает этот метод после того, как на данный объект больше нет действующих ссылок.

В некоторых случаях вы можете предоставить использующим объект программистам возможность явно освободить эти внешние ресурсы, перед тем как сборщик мусора уничтожит объект. Если внешние ресурсы дефицитны или дороги, наилучшей производительности можно достичь при явном освобождении ресурсов программистом, когда они уже не используются. Явное управление реализуется при помощи Dispose, предоставляемого IDisposable. Пользователь объекта должен вызывать этот метод при завершении использования объекта. Dispose можно вызывать, даже если действительны другие ссылки на объект.

Обратите внимание, что даже при наличии явного управления с помощью метода Dispose следует предоставить и неявное освобождение ресурсов с использованием метода Finalize. Метод Finalize предоставляет дополнительный способ предотвращения потери ресурсов в случае, если программист забыл вызывать Dispose.

Дополнительную информацию о реализации Finalize и Dispose для освобождения внешних ресурсов см. Сборка мусора. В следующем примере показан базовый шаблон для реализации распоряжаться. В этом примере требуется использовать пространство имен System.

' Design pattern for a base class.

Public Class Base
   Implements IDisposable
    ' Field to handle multiple calls to Dispose gracefully.
    Dim disposed as Boolean = false

   ' Implement IDisposable.
   Public Overloads Sub Dispose() Implements IDisposable.Dispose
      Dispose(True)
      GC.SuppressFinalize(Me)
   End Sub

   Protected Overloads Overridable Sub Dispose(disposing As Boolean)
      If disposed = False Then
          If disposing Then
             ' Free other state (managed objects).
             disposed = True
          End If
          ' Free your own state (unmanaged objects).
          ' Set large fields to null.
      End If
   End Sub

   Protected Overrides Sub Finalize()
      ' Simply call Dispose(False).
      Dispose (False)
   End Sub
End Class

' Design pattern for a derived class.
Public Class Derived
   Inherits Base

    ' Field to handle multiple calls to Dispose gracefully.
    Dim disposed as Boolean = false

   Protected Overloads Overrides Sub Dispose(disposing As Boolean)
      If disposed = False Then
          If disposing Then
             ' Release managed resources.
          End If
          ' Release unmanaged resources.
          ' Set large fields to null.
          disposed = True
      End If
      ' Call Dispose on your base class.
      Mybase.Dispose(disposing)
   End Sub
   ' The derived class does not have a Finalize method
   ' or a Dispose method without parameters because it inherits
   ' them from the base class.
End Class
// Design pattern for a base class.
public class Base: IDisposable
{
    private bool disposed = false;

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

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Free other state (managed objects).
            }
            // Free your own state (unmanaged objects).
            // Set large fields to null.
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code.
    ~Base()
    {
        // Simply call Dispose(false).
        Dispose (false);
    }
}

// Design pattern for a derived class.
public class Derived: Base
{
    private bool disposed = false;

    protected override void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Release managed resources.
            }
            // Release unmanaged resources.
            // Set large fields to null.
           // Call Dispose on your base class.
            disposed = true;
        }
        base.Dispose(disposing);
    }
    // The derived class does not have a Finalize method
    // or a Dispose method without parameters because it inherits
    // them from the base class.
}

Следующий код расширяет предыдущий пример, чтобы показать различные способы распоряжаться вызывается и когда Finalize называется. На этапах disposing шаблона, отслеживаются с выходом на консоль. Выделение и освобождение неуправляемый ресурс обрабатывается в производном классе.

Imports System
Imports System.Collections.Generic
Imports System.Runtime.InteropServices

' Design pattern for a base class.
Public MustInherit Class Base
    Implements IDisposable

    Private disposed as Boolean = false
    Private instName As String
    Private trackingList As List(Of Object)

    Public Sub New(instanceName As String, tracking As List(Of Object))
        MyClass.instName = instanceName
        trackingList = tracking
        trackingList.Add(Me)
    End Sub

    Public ReadOnly Property InstanceName() As String
        Get
            Return instName
        End Get
    End Property

    'Implement IDisposable.
    Public Overloads Sub Dispose() Implements IDisposable.Dispose
        Console.WriteLine(vbNewLine + "[{0}].Base.Dispose()", instName)
        Dispose(true)
        GC.SuppressFinalize(Me)
    End Sub

    Protected Overloads Overridable Sub Dispose(disposing As Boolean)
        If disposed = False Then
            If disposing Then
                ' Free other state (managed objects).
                Console.WriteLine("[{0}].Base.Dispose(true)", instName)
                trackingList.Remove(Me)
                Console.WriteLine("[{0}] Removed from tracking list: {1:x16}",
                    instanceName, MyClass.GetHashCode())
            Else
                Console.WriteLine("[{0}].Base.Dispose(false)", instName)
            End If
            disposed = True
        End If
    End Sub

    Protected Overrides Sub Finalize()
        ' Simply call Dispose(False).
        Console.WriteLine(vbNewLine + "[{0}].Base.Finalize()", instName)
        Dispose(False)
    End Sub
End Class

' Design pattern for a derived class.
Public Class Derived
   Inherits Base

    Private disposed as Boolean = false
    Private umResource As IntPtr

    Public Sub New(instanceName As String, tracking As List(Of Object))
        MyBase.New(instanceName, tracking)
        ' Save the instance name as an unmanaged resource
        umResource = Marshal.StringToCoTaskMemAuto(instanceName)
    End Sub

    Protected Overloads Overrides Sub Dispose(disposing As Boolean)
        If disposed = False Then
            If disposing Then
                Console.WriteLine("[{0}].Derived.Dispose(true)", InstanceName)
                ' Release managed resources.
            Else
                Console.WriteLine("[{0}].Derived.Dispose(false)", InstanceName)
            End If
           ' Release unmanaged resources.
            If umResource <> IntPtr.Zero
                Marshal.FreeCoTaskMem(umResource)
                Console.WriteLine("[{0}] Unmanaged memory freed at {1:x16}", _
                    InstanceName, umResource.ToInt64())
                umResource = IntPtr.Zero
            End If
            disposed = True
        End If
        ' Call Dispose in the base class.
        MyBase.Dispose(disposing)
    End Sub
    ' The derived class does not have a Finalize method
    ' or a Dispose method without parameters because it inherits
    ' them from the base class.
End Class

Public Class TestDisposal
    Public Shared Sub Main()
        Dim tracking As New List(Of Object)()

        ' Dispose is not called, Finalize will be called later.
        Using Nothing
            Console.WriteLine(vbNewLine + "Disposal Scenario: #1" + vbNewLine)
            Dim d3 As New Derived("d1", tracking)
        End Using

        ' Dispose is implicitly called in the scope of the using statement.
        Using d1 As New Derived("d2", tracking)
            Console.WriteLine(vbNewLine + "Disposal Scenario: #2" + vbNewLine)
        End Using

        ' Dispose is explicitly called.
        Using Nothing
            Console.WriteLine(vbNewLine + "Disposal Scenario: #3" + vbNewLine)
            Dim d2 As New Derived("d3", tracking)
            d2.Dispose()
        End Using

        ' Again, Dispose is not called, Finalize will be called later.
        Using Nothing
            Console.WriteLine(vbNewLine + "Disposal Scenario: #4" + vbNewLine)
            Dim d4 As New Derived("d4", tracking)
        End Using

        ' List the objects remaining to dispose.
        Console.WriteLine(vbNewLine + "Objects remaining to dispose = {0:d}", tracking.Count)
        For Each dd As Derived in tracking
            Console.WriteLine("    Reference Object: {0:s}, {1:x16}",
                dd.InstanceName, dd.GetHashCode())
        Next dd
        ' Queued finalizers will be exeucted when Main() goes out of scope.
        Console.WriteLine(vbNewLine + "Dequeueing finalizers...")
    End Sub
End Class

' The program will display output similar to the following:
'
' Disposal Scenario: #1
'
'
' Disposal Scenario: #2
'
'
' [d2].Base.Dispose()
' [d2].Derived.Dispose(true)
' [d2] Unmanaged memory freed at 00000000001ce420
' [d2].Base.Dispose(true)
' [d2] Removed from tracking list: 0000000002bf8098
'
' Disposal Scenario: #3
'
'
' [d3].Base.Dispose()
' [d3].Derived.Dispose(true)
' [d3] Unmanaged memory freed at 00000000001ce420
' [d3].Base.Dispose(true)
' [d3] Removed from tracking list: 0000000000bb8560
'
' Disposal Scenario: #4
'
'
' Objects remaining to dispose = 2
'     Reference Object: d1, 000000000297b065
'     Reference Object: d4, 0000000003553390
'
' Dequeueing finalizers...
'
' [d4].Base.Finalize()
' [d4].Derived.Dispose(false)
' [d4] Unmanaged memory freed at 00000000001ce420
' [d4].Base.Dispose(false)
'
' [d1].Base.Finalize()
' [d1].Derived.Dispose(false)
' [d1] Unmanaged memory freed at 00000000001ce3f0
' [d1].Base.Dispose(false)
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

// Design pattern for a base class.
public abstract class Base : IDisposable
{
    private bool disposed = false;
    private string instanceName;
    private List<object> trackingList;

    public Base(string instanceName, List<object> tracking)
    {
        this.instanceName = instanceName;
         trackingList = tracking;
         trackingList.Add(this);
    }

    public string InstanceName
    {
        get
        {
            return instanceName;
        }
    }

    //Implement IDisposable.
    public void Dispose()
    {
        Console.WriteLine("\n[{0}].Base.Dispose()", instanceName);
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Free other state (managed objects).
                Console.WriteLine("[{0}].Base.Dispose(true)", instanceName);
                trackingList.Remove(this);
                Console.WriteLine("[{0}] Removed from tracking list: {1:x16}",
                    instanceName, this.GetHashCode());
            }
            else
            {
                Console.WriteLine("[{0}].Base.Dispose(false)", instanceName);
            }
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code.
    ~Base()
    {
        // Simply call Dispose(false).
        Console.WriteLine("\n[{0}].Base.Finalize()", instanceName);
        Dispose(false);
    }
}

// Design pattern for a derived class.
public class Derived : Base
{
    private bool disposed = false;
    private IntPtr umResource;

    public Derived(string instanceName, List<object> tracking) :
        base(instanceName, tracking)
    {
         // Save the instance name as an unmanaged resource
         umResource = Marshal.StringToCoTaskMemAuto(instanceName);
    }

    protected override void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                Console.WriteLine("[{0}].Derived.Dispose(true)", InstanceName);
                // Release managed resources.
            }
            else
            {
                Console.WriteLine("[{0}].Derived.Dispose(false)", InstanceName);
            }
            // Release unmanaged resources.
            if (umResource != IntPtr.Zero)
            {
                Marshal.FreeCoTaskMem(umResource);
                Console.WriteLine("[{0}] Unmanaged memory freed at {1:x16}",
                    InstanceName, umResource.ToInt64());
                umResource = IntPtr.Zero;
            }
            disposed = true;
        }
        // Call Dispose in the base class.
        base.Dispose(disposing);
    }
    // The derived class does not have a Finalize method
    // or a Dispose method without parameters because it inherits
    // them from the base class.
}

public class TestDisposal
{
    public static void Main()
    {
        List<object> tracking = new List<object>();

        // Dispose is not called, Finalize will be called later.
        using (null)
        {
            Console.WriteLine("\nDisposal Scenario: #1\n");
            Derived d3 = new Derived("d1", tracking);
        }

        // Dispose is implicitly called in the scope of the using statement.
        using (Derived d1 = new Derived("d2", tracking))
        {
            Console.WriteLine("\nDisposal Scenario: #2\n");
        }

        // Dispose is explicitly called.
        using (null)
        {
            Console.WriteLine("\nDisposal Scenario: #3\n");
            Derived d2 = new Derived("d3", tracking);
            d2.Dispose();
        }

        // Again, Dispose is not called, Finalize will be called later.
        using (null)
        {
            Console.WriteLine("\nDisposal Scenario: #4\n");
            Derived d4 = new Derived("d4", tracking);
        }

        // List the objects remaining to dispose.
        Console.WriteLine("\nObjects remaining to dispose = {0:d}", tracking.Count);
        foreach (Derived dd in tracking)
        {
            Console.WriteLine("    Reference Object: {0:s}, {1:x16}",
                dd.InstanceName, dd.GetHashCode());
        }

        // Queued finalizers will be exeucted when Main() goes out of scope.
        Console.WriteLine("\nDequeueing finalizers...");
    }
}

// The program will display output similar to the following:
//
// Disposal Scenario: #1
//
//
// Disposal Scenario: #2
//
//
// [d2].Base.Dispose()
// [d2].Derived.Dispose(true)
// [d2] Unmanaged memory freed at 000000000034e420
// [d2].Base.Dispose(true)
// [d2] Removed from tracking list: 0000000002bf8098
//
// Disposal Scenario: #3
//
//
// [d3].Base.Dispose()
// [d3].Derived.Dispose(true)
// [d3] Unmanaged memory freed at 000000000034e420
// [d3].Base.Dispose(true)
// [d3] Removed from tracking list: 0000000000bb8560
//
// Disposal Scenario: #4
//
//
// Objects remaining to dispose = 2
//    Reference Object: d1, 000000000297b065
//    Reference Object: d4, 0000000003553390
//
// Dequeueing finalizers...
//
// [d4].Base.Finalize()
// [d4].Derived.Dispose(false)
// [d4] Unmanaged memory freed at 000000000034e420
// [d4].Base.Dispose(false)
//
// [d1].Base.Finalize()
// [d1].Derived.Dispose(false)
// [d1] Unmanaged memory freed at 000000000034e3f0
// [d1].Base.Dispose(false)

В качестве примера дополнительного кода, иллюстрирующий шаблон разработки для реализации Finalize и распоряжаться, см. реализации метода Dispose.

Настройка имени метода Dispose

В ряде случаев имя, зависимое от домена, подходит лучше, чем Dispose. Например, для инкапсуляции файла может потребоваться применение Close в качестве имени метода. В этом случае реализуйте закрытый метод Dispose и создайте открытый метод Close, вызывающий Dispose. В следующем примере кода показан этот шаблон. Имя Close можно заменить именем, подходящим для вашего домена. В этом примере требуется использовать пространство имен System.

' Do not make this method overridable.
' A derived class should not be allowed
' to override this method.
Public Sub Close()
   ' Call the Dispose method with no parameters.
   Dispose()
End Sub
// Do not make this method virtual.
// A derived class should not be allowed
// to override this method.
public void Close()
{
   // Call the Dispose method with no parameters.
   Dispose();
}

Finalize

Следующие правила определяют рекомендации по использованию метода Finalize.

  • Реализуйте Finalize только в объектах, требующих завершения. Методы Finalize оказывают заметное влияние на производительность.

  • Если необходим метод Finalize, следует рассмотреть возможность реализации IDisposable, чтобы избавить пользователей класса от затрат, связанных с вызовом метода Finalize.

  • Не следует увеличивать область видимости метода Finalize. Он должен быть protected, а не public.

  • Метод Finalize объекта должен освобождать любые внешние ресурсы, которыми владеет объект. Более того, метод Finalize должен высвобождать ресурсы, захваченные данным объектом. Метод Finalize не должен ссылаться на какие-либо иные объекты.

  • Не допускайте прямых вызовов метода Finalize для объектов, отличных от объектов базового класса. В языке программирования C# такая операция недопустима.

  • Вызывайте метод Finalize базового класса из метода Finalize объекта.

    ПримечаниеПримечание

    В синтаксисе деструкторов C# и C++ метод Finalize базового класса вызывается автоматически.

Dispose

Ниже приведены рекомендации по использованию метода Dispose.

  • Реализуйте шаблон освобождения ресурсов для типа, инкапсулирующего ресурсы, требующие освобождения в явном виде. Пользователи могут освобождать внешние ресурсы с помощью вызова открытого метода Dispose.

  • Реализуйте шаблон освобождения ресурсов для базового типа, даже если базовый тип их не захватывает, так как производные типы могут захватывать ресурсы. Наличие в базовом типе метода Close часто указывает на необходимость реализации метода Dispose. В таких случаях для базового типа не следует реализовывать метод Finalize. Метод Finalize следует реализовать в любых производных типах, в которых добавляются ресурсы, требующие освобождения.

  • В методе Dispose освобождайте любые ресурсы, которыми владеет тип и которые можно освободить.

  • После вызова Dispose для экземпляра предотвратите выполнение метода Finalize путем вызова GC.SuppressFinalize. Исключениями из этого правила являются редкие ситуации, в которых метод Finalize должен выполнить действия, не выполненные ранее методом Dispose.

  • Вызовите метод Dispose базового класса, если класс реализует IDisposable.

  • Не следует предполагать, что будет вызван метод Dispose. Внешние ресурсы, которыми владеет тип, должны освобождаться в методе Finalize так, как если бы не был вызван метод Dispose.

  • Генерируйте исключение ObjectDisposedException из экземпляров методов этого типа (отличных от Dispose), если ресурсы были освобождены. Это правило неприменимо к методу Dispose, поскольку он должен вызываться многократно без генерации исключения.

  • Транслируйте вызовы Dispose через иерархию базовых типов. Метод Dispose должен освобождать все ресурсы, удерживаемые данным объектом и любым объектом, которым он владеет. Например, можно создать объект, такой как TextReader, который захватывает Stream и Encoding, которые создаются TextReader без участия пользователя. Более того, как Stream, так и Encoding могут запрашивать внешние ресурсы. При вызове метода Dispose в TextReader он должен, в свою очередь, вызывать Dispose в Stream и Encoding, чтобы освободить внешние ресурсы.

  • Имейте в виду, что объект не может быть использован после вызова его метода Dispose. Повторное создание объекта, ресурсы которого были освобождены, представляет собой сложный для реализации шаблон.

  • Следует разрешить многократный вызов метода Dispose без генерации исключений. Этот метод не должен делать что-либо после первого вызова.

Охраняется авторским правом Copyright 2005 Microsoft Corporation. Все права защищены.

Фрагменты — © Addison-Wesley Corporation. Все права защищены.

Для дополнительной информации о разработке руководящих принципов, смотрите "руководства по разработке рамок: Конвенций, идиомы и шаблоны для повторного использования.NET библиотек"книга, Кшиштоф Cwalina и Брэд Абрамс, опубликованных Addison-Wesley, 2005 года.

См. также

Ссылки

IDisposable.Dispose

Object.Finalize

Основные понятия

Сборка мусора

Другие ресурсы

Руководство по разработке библиотек классов

Шаблоны разработки