1

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

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

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

Если коротко, то они сводятся к «зачем так сложно?» и «как правильно должны освобождать свою память потомки?»

И в самом деле. Есть две реализации. Простая:

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

* This source code was highlighted with Source Code Highlighter.

И сложная:

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.

Причем сложная реализация существует в двух вариантах. Первый вариант предлагает МСДН по этой ссылке, а второй (для поддержки наследования) вот поэтой. Вторая реализация выглядит вот так:

public class Base: IDisposable
{
  public void Dispose() 
  {
   Dispose(true);
   GC.SuppressFinalize(this); 
  }

  protected virtual void Dispose(bool disposing) 
  {
   if (disposing) 
   {
     //вызов Dispose для managed переменных
   }
   //освобождение unmanaged ресурсов
  }

  ~Base()
  {
   Dispose (false);
  }
}

//потомок
public class Derived: Base
{  
 protected override void Dispose(bool disposing) 
 {
  if (disposing) 
  {
   //вызов Dispose для managed переменных
  }
  //освобождение unmanaged ресурсов

  //вызов Dispose(bool) базового класса
  base.Dispose(disposing);
 }

 //в потомках не перекрывайте метод Dispose без параметров
 //и деструктор - они уже описаны в родительском классе.
}


* This source code was highlighted with Source Code Highlighter.

Отличие, как легко видеть, только в том что метод Dispose(bool) из закрытого превратился в защищенный и виртуальный. Попробуем разобраться, зачем так сложно? И зачем это надо знать?

Отвечу сначала на второй вопрос: предложенный вариант освобождения ресурсов нужно знать, если вы наследуетесь от стандартных классов, которые уже реализовали интерфейс IDisposable. Примером может служить System.Windows.Forms.UserControl, у которого УЖЕ реализован метод Dispose.

Простой случай: вы породили свой контрол от UserControl. И ваш контрол владеет unmanaged ресурсом. Например он умеет выводить изображение при помощи OpenGL и для этого ему нужно управлять контекстом рендеринга. И не только управлять, а еще и корректно освобождать его. В этом случае, перекрыв метод Dispose у родительского класса, вы вмешаетесь в схему освобождения ресурсов, что, очевидно, плохо.

В этом случае, можно решить проблему путем взаимодействия с тем механизмом освобождения ресурсов, который предложила MS. А решение их выглядит достаточно просто:

1) Есть метод для ручной очистки «прямо сейчас и немедленно»: Dispose()
2) Есть метод для очистки «пора убирать мусор» (вызывается из GC): деструктор
3) И тот и другой метод должны выполнять очистку, которая вынесена в отдельный метод: Dispose(bool)

Метод Dispose(bool), который и выполняет очистку, на вход получает параметр, позволяющий отличить очистку от «прямо сейчас и немедленно» от «пора убирать мусор». Зачем? А потому что ваш класс может владеть managed член-переменными, которые владеют unmanaged ресурсами. И чтобы они могли освободить unmanaged ресурсы, для них тоже нужно вызвать Dispose. В отличие от ситуации, когда уборкой занимается GC. В этом случае требования освободить ресурсы «прямо сейчас и немедленно» не поступало и можно положиться на GC.

В реализации, предложенной для случая с наследованием от MSDN есть еще одна особенность: отсутствует переменная-флаг disposed. Т.е. нет защиты от множественного вызова Dispose. Что не очень красиво. Есть немного отличная реализация, предложенная в этой статье:

using System;

namespace RSDN
{
 public abstract class DisposableType: IDisposable
 {
  bool disposed = false;

  ~DisposableType()
  {
   if (!disposed) 
   {
    disposed = true;
    Dispose(false);
   }
  }

  public void Dispose()
  {
   if (!disposed) 
   {
    disposed = true;
    Dispose(true);
    GC.SuppressFinalize(this);
   }
  }

  public void Close()
  {
   Dispose();
  }

  protected virtual void Dispose(bool disposing)
  {
   if (disposing) 
   {
    // managed objects
   }
   // unmanaged objects and resources
  }
 }
}

* This source code was highlighted with Source Code Highlighter.

Метки:

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

  1. букмекеры,

    Первый вариант — МСДН мне больше приглянулся, но есть свои нюансы…

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