1

О бедном Dispose замолвите слово (часть 1)

Андрей Трусов
8 мая 2009 года

Немного про освобождение ресурсов в .Net.

предупреждение: текст ниже — просто пересказ своими словами давно известной информации, которая есть в сети на русском и английском языках.

Что делать, когда хочется освободить unmanaged ресурсы в .Net? Можно поместить код освобождения ресурсов в секцию finally и это будет самый простой способ. Не очень изящный, но зато гарантированно сработает и без всяких подводных камней:

DBConnection conn = new DBConnection();
try
{
 conn.Open();
 //...
}
finally
{
 conn.Close();
}

* This source code was highlighted with Source Code Highlighter.

Усложним задачу. Мы написали библиотечный класс, который работает с unmanaged ресурсами. По закону подлости (или больших чисел — как вам удобнее), пользоваться им, кроме гуру, будут еще и говнокодеры. Которые спокойно забудут вызвать метод для освобождения unmanaged ресурсов. А потом будут плеваться на криво написанную ВАМИ библиотеку.

А значит хочется написать свой класс таким образом, чтобы метод, освобождающий ресурсы (уже не важно — managed они или unmanaged) был вызван сборщиком мусора автоматически в том случае, если пользователь забыл вызвать его сам.

Для освобождения unmanaged ресурсов предлагается использовать интерфейс IDisposable с одной единственной функцией Dispose, которая и должна освобождать ресурсы. И основная задача здесь — не написание этой функции, а написание кода так, чтобы эта функция была вызвана обязательно.

Имеем два случая:
1) Пользователь вызвал метод, освобождающий ресурсы сам
2) Пользователь ресурсы освобождать не просил и этим должен заняться GC

Первый вариант выглядит примерно так:

using (MyClass x = new MyClass())
{
 x.SomeMethod();
 //...
}


* This source code was highlighted with Source Code Highlighter.

или так:

MyClass x;
try
{
 x = new MyClass();
 x.SomeMethod();
 //...
}
finally
{
 if (x != null)
  x.Dispose();
}

* This source code was highlighted with Source Code Highlighter.

Второй вариант выглядит так:

MyClass x = new MyClass();
x.SomeMethod();
//...


* This source code was highlighted with Source Code Highlighter.

Подумаем, как реализовать сам класс MyClass и его метод Dispose, чтобы во всех этих случаях наш код работал? Перечислим требования, которые лежат на поверхности:
1) метод Dispose не должен падать, если его вызовут несколько раз подряд (защита от дурака)
2) метод Dispose не должен вызываться из GC, если его вызвали явно (и, соответственно, должен вызываться из GC, если пользователь не вызвал его сам).

Именно из этих двух соображений и появилась в предлагаемой реализации класса MyClass переменная disposed (чтобы суметь отличить уже освобожденный класс от не освобожденного), деструктор (чтобы вызвать метод Dispose из GC) и, наконец, обертка над функцией Dispose — функция с bool параметром Dispose(bool manual) (чтобы отличать ручной вызов Dispose от автоматического). Ах да. А отличать ручной вызов от автоматического нужно для того, чтобы при помощи метода GC.SuppressFinalize(this); отключить вызов деструктора.

Итого получаем такой класс (простой копипаст из msdn):

public class MyResource: IDisposable
{
 private bool disposed = false;

 //реализация интерфейса IDisposable
 //не делайте эту функцию виртуальной
 //и не перекрывайте в потомках
 public void Dispose()
 {
  Dispose(true);             //освобождение вызвали вручную
  GC.SuppressFinalize(this); //не хотим, чтобы GC вызвал деструктор
 }

 //деструктор
 ~MyClass()
 {
  Dispose(false); //освобождения ресурсов потребовал GC вызвав деструтор
 }

 //освобождаем ресурсы
 private void Dispose(bool manual)
 {
  if (!this.disposed)
  {
   if (manual)
   {
    //освобождаем managed ресурсы
    //...

    //иными словами - вызываем метод Dispose
    //для всех managed член-переменных класса
    //это нужно делать только для ручного вызова Dispose,
    //потому что в другом случае случае Dispose для них вызовет GC
   }

   //освобождаем unmanaged ресурсы
   //...

   disposed = true;
  }
 }
}

* This source code was highlighted with Source Code Highlighter.

Вот и все. Есть еще небольшое усложнение этого кода, если вы наследуетесь от класса MyClass (там требуется обеспечить вызов метода Dispose у родительского класса, путем перекрытия метода Dispose(bool)). Но это уже не так сложно.

Метки:

Один коментарий к записи «О бедном Dispose замолвите слово (часть 1)»

  1. dasha,

    Я ничего не поняла, но всё равно молодец, Андрей! :)

Оставить комментарий