Родовая функция для обработки расположения объекты IDisposable

Я работаю над классом, который имеет дело с большим количеством объектов Sql - Соединение, Команда, 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;
    }
}

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

  1. Действительно ли эта родовая функция является плохой идеей?
  2. Действительно ли необходимо установить объект на null?

Править:

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

Спасибо!

11
задан Jerod Houghtelling 6 July 2010 в 20:24
поделиться

9 ответов

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

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

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

Обязательно ли устанавливать для объекта значение NULL?

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

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

1
ответ дан 3 December 2019 в 06:20
поделиться

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

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

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

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

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

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

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

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

Пара деталей реализации:

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

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

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

6
ответ дан 3 December 2019 в 06:20
поделиться

Если у ваших объектов много работы по очистке, они могут захотеть отследить, что нужно удалить, в отдельном списке одноразового использования и обработать все сразу. Затем при разборке ему не нужно запоминать все, что нужно удалить (и не нужно проверять значение 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();
    }
}
3
ответ дан 3 December 2019 в 06:20
поделиться

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

1
ответ дан 3 December 2019 в 06:20
поделиться

Почему вы не используете конструкцию using C #? http://msdn.microsoft.com/en-us/library/yh598w02.aspx Значение null, если не требуется.

0
ответ дан 3 December 2019 в 06:20
поделиться

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

В этом случае ваш метод совершенно нормален.

Что касается второй части вашего вопроса ("Необходимо ли устанавливать его в null?"), то простой ответ: "Нет, но это ничему не повредит".

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

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

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

1
ответ дан 3 December 2019 в 06:20
поделиться

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

private static void DisposeObject( ref IDisposable disposableObject )
{
    if( disposableObject != null )
    {
        disposableObject.Dispose();
        disposableObject = null;
    }
}
0
ответ дан 3 December 2019 в 06:20
поделиться

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

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

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

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

0
ответ дан 3 December 2019 в 06:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: