Общая функция для обработки объектов IDisposable

Я работаю над классом, который имеет дело с большим количеством объектов Sql - Connection, Command, DataAdapter, CommandBuilder и т.д. Есть несколько экземпляров, где у нас есть такой код:

if( command != null )
{
    command.Dispose();
}

if( dataAdapter != null )
{
    dataAdapter.Dispose();
}

etc

Я знаю, что этого недостаточно, с точки зрения дублирования, но он начал пахнуть. Причина, по которой я думаю, что это пахнет, заключается в том, что в некоторых случаях объект также имеет значение null.

if( command != null )
{
    command.Dispose();
    command = null;
}

Я хотел бы, если возможно, избавиться от дублирования. Я придумал этот общий метод, чтобы избавиться от объекта и установить его в null.

private void DisposeObject<TDisposable>( ref TDisposable disposableObject )
    where TDisposable : class, IDisposable
{
    if( disposableObject != null )
    {
        disposableObject.Dispose();
        disposableObject = null;
    }
}

Мои вопросы...

  • Является ли эта общая функция плохой идеей?
  • Нужно ли устанавливать объект в null?

EDIT:

Я знаю инструкцию using, но я не могу ее всегда использовать, потому что у меня есть некоторые переменные-члены, которые должны сохраняться дольше, чем один вызов. Например, объекты соединения и транзакции.

Спасибо!

Ответ 1

Я предполагаю, что это поля, а не локальные переменные, поэтому почему ключевое слово using не имеет смысла.

Является ли эта общая функция плохой идеей?

Я думаю, что это хорошая идея, и я использовал подобную функцию несколько раз; +1 для создания его общего.

Нужно ли устанавливать объект в null?

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

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

Ответ 2

Вам следует подумать, можете ли вы использовать оператор using.

using (SqlCommand command = ...)
{
    // ...
}

Это гарантирует, что Dispose вызывается в объекте команды, когда элемент управления выходит за пределы области использования. Это имеет ряд преимуществ перед написанием кода очистки, как вы это делали:

  • Это более краткий.
  • Переменная никогда не будет установлена ​​в null - она ​​объявляется и инициализируется в одном выражении.
  • При удалении объекта переменная выходит за пределы области, поэтому вы рискуете случайно получить доступ к расположенному ресурсу.
  • Это безопасно.
  • Если вы гнездились с помощью операторов, то ресурсы естественно расположены в правильном порядке (в обратном порядке, в котором они были созданы).

Нужно ли устанавливать объект в null?

Обычно нет необходимости устанавливать переменные в значение null, когда вы закончите использовать их. Важно то, что вы вызываете Dispose, когда закончите использовать ресурс. Если вы используете приведенный выше шаблон, то не обязательно устанавливать переменную в нуль - это даст ошибку компиляции:

Cannot assign to 'c' because it is a 'using variable'

Следует отметить, что using работает только в том случае, если объект получен и расположен в том же вызове метода. Вы не можете использовать этот шаблон, если ваши ресурсы должны оставаться в живых в течение более одного вызова метода. В этом случае вы можете сделать свой класс реализованным IDisposable и убедиться, что ресурсы очищены при вызове метода Dispose. В этом случае вам понадобится код, как вы написали. Установка переменных в null не является ошибкой в ​​этом случае, но это не важно, потому что сборщик мусора в любом случае очистит память. Важно, чтобы все ваши ресурсы были утилизированы при вызове метода dispose, и вы это делаете.

Несколько деталей реализации:

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

Ответ 3

Вы должны реализовать IDisposable в классе, которому принадлежат эти поля. См. мой пост в блоге по этому вопросу. Если это не работает, то класс не следует принципам ООП и нуждается в рефакторинге.

не требуется для установки переменных null после их удаления.

Ответ 4

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

Это, вероятно, не построено, но для расширенных целей вы можете включить RecycleBin в свой класс. Затем классу нужно только удалить корзину.

public class RecycleBin : IDisposable
{
    private List<IDisposable> _toDispose = new List<IDisposable>();

    public void RememberToDispose(IDisposable disposable)
    {
        _toDispose.Add(disposable);
    }

    public void Dispose()
    {
        foreach(var d in _toDispose)
            d.Dispose();

        _toDispose.Clear();
    }
}

Ответ 5

Я собираюсь предположить, что вы создаете ресурс одним методом, удаляя его в другом и используя его в одном или нескольких других, делая оператор using бесполезным для вас.

В этом случае ваш метод идеально подходит.

В качестве второй части вашего вопроса ( "Нужно ли устанавливать нулевое значение?" ), простой ответ: "Нет, но это ничего не мешает".

В большинстве объектов есть один ресурс - память, с которой коллекция мусора занимается освобождением, поэтому нам не нужно беспокоиться об этом. Некоторые из них содержат и другой ресурс: дескриптор файла, соединение с базой данных и т.д. Для второй категории мы должны реализовать IDisposable, чтобы освободить этот другой ресурс.

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

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

Ответ 6

Учитывая, что iDisposable не содержит стандартного способа определения того, был ли объект удален, мне нравится задавать значение null при его удалении. Разумеется, удаление объекта, который уже был установлен, безопасен, но приятно иметь возможность исследовать объект в окне часов и сразу же определить, какие поля были удалены. Также приятно иметь код для проверки того, что объекты, которые должны были быть удалены, (при условии, что код придерживается соглашения об отключении переменных при их удалении и ни в какое другое время).

Ответ 8

Другие рекомендовали конструкцию using, которую я также рекомендую. Тем не менее, я хотел бы отметить, что даже если вам нужен утилитарный метод, совершенно не нужно делать его обобщенным так, как вы это делали. Просто объявите свой метод для принятия IDisposable:

private static void DisposeObject( ref IDisposable disposableObject )
{
    if( disposableObject != null )
    {
        disposableObject.Dispose();
        disposableObject = null;
    }
}

Ответ 9

Вам не нужно устанавливать переменные в null. Вся точка IDisposable.Dispose заключается в том, чтобы объект попал в состояние, благодаря которому он мог без опасностей зависнуть в памяти до тех пор, пока GC не завершит его, поэтому вы просто "уничтожаете и забываете".

Мне любопытно, почему вы думаете, что не можете использовать оператор using. Создание объекта в одном методе и его использование в другом методе - это сильный сильный запах кода в моей книге, потому что вы скоро теряете связь с тем, что открыто где. Лучше реорганизовать код следующим образом:

using(var xxx = whatever()) {
    LotsOfProcessing(xxx);
    EvenMoreProcessing(xxx);
    NowUseItAgain(xxx);
}

Я уверен, что для этого есть стандартное имя шаблона, но я просто называю его "уничтожить все, что вы создаете, но ничего больше".