Высвобождаемые объекты

71

Методы финализации могут применяться для освобождения неуправляемых ресурсов при активизации процесса сборки мусора. Однако многие неуправляемые объекты являются "ценными элементами" (например, низкоуровневые соединения с базой данных или файловые дескрипторы) и часто выгоднее освобождать их как можно раньше, еще до наступления момента сборки мусора. Поэтому вместо переопределения Finalize() в качестве альтернативного варианта также можно реализовать в классе интерфейс IDisposable, который имеет единственный метод по имени Dispose():

public interface IDisposable
{
  void Dispose();
}

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

Интерфейс IDisposable может быть реализован как в классах, так и в структурах (в отличие от метода Finalize(), который допускается переопределять только в классах), потому что метод Dispose() вызывается пользователем объекта (а не сборщиком мусора). Рассмотрим пример использования этого интерфейса:

using System;

namespace ConsoleApplication1
{
    // Данный класс реализует интерейс IDisposable
    class FinalizeObject : IDisposable
    {
        public int id { get; set; }

        public FinalizeObject(int id)
        {
            this.id = id;
        }

        // Реализуем метод Dispose()
        public void Dispose()
        {
            Console.WriteLine("Высвобождение объекта!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            FinalizeObject obj = new FinalizeObject(4);
            obj.Dispose();

            Console.Read();
        }
    }
}

Обратите внимание, что метод Dispose() отвечает не только за освобождение неуправляемых ресурсов типа, но и за вызов аналогичного метода в отношении любых других содержащихся в нем высвобождаемых объектов. В отличие от Finalize(), в нем вполне допустимо взаимодействовать с другими управляемыми объектами. Объясняется это очень просто: сборщик мусора не имеет понятия об интерфейсе IDisposable и потому никогда не будет вызывать метод Dispose(). Следовательно, при вызове данного метода пользователем объект будет все еще существовать в управляемой куче и иметь доступ ко всем остальным находящимся там объектам.

Этот пример раскрывает еще одно правило относительно работы с подвергаемыми сборке мусора типами: для любого создаваемого напрямую объекта, если он поддерживает интерфейс IDisposable, следует всегда вызывать метод Dispose(). Необходимо исходить из того, что в случае, если разработчик класса решил реализовать метод Dispose(), значит, классу надлежит выполнять какую-то очистку.

Повторное использование ключевого слова using в C#

При работе с управляемым объектом, который реализует интерфейс IDisposable, довольно часто требуется применять структурированную обработку исключений, гарантируя, что метод Dispose() типа будет вызываться даже в случае возникновения какого-то исключения:

FinalizeObject obj = new FinalizeObject(4);
try
{
    // Выполнение необходимых операций
}
finally
{
    obj.Dispose();
}

Хотя это является замечательными примером "безопасного программирования", истина состоит в том, что очень немногих разработчиков прельщает перспектива заключать каждый очищаемый тип в блок try/finally лишь для того, чтобы гарантировать вызов метода Dispose(). Для достижения аналогичного результата, но гораздо менее громоздким образом, в C# поддерживается специальный фрагмент синтаксиса, который выглядит следующим образом:

using (FinalizeObject obj = new FinalizeObject(4))
{
    // Необходимые действия
}

Если теперь просмотреть CIL-код этого метода Main() с помощью утилиты ildasm.ехе, то обнаружится, что синтаксис using в таких случаях на самом деле расширяется до логики try/finally, которая включает в себя и ожидаемый вызов Dispose():

CIL-код высвобождаемых объектов C#

Хотя применение такого синтаксиса действительно избавляет от необходимости вручную помещать высвобождаемые объекты в рамки try/finally, в настоящее время, к сожалению, ключевое слово using в C# имеет двойное значение (поскольку служит и для добавления ссылки на пространства имен, и для вызова метода Dispose()). Тем не менее, при работе с типами .NET, которые поддерживают интерфейс IDisposable, данная синтаксическая конструкция будет гарантировать автоматический вызов метода Dispose() в отношении соответствующего объекта при выходе из блока using.

Кроме того, в контексте using допускается объявлять несколько объектов одного и того же типа. Как не трудно догадаться, в таком случае компилятор будет вставлять код с вызовом Dispose() для каждого объявляемого объекта.

Пройди тесты
Лучший чат для C# программистов