После опубликования предыдущей моей записи про 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.
Метки: .net
Первый вариант — МСДН мне больше приглянулся, но есть свои нюансы…