Я работаю над классом, который имеет дело с большим количеством объектов 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;
}
}
Мои вопросы...
null
?Править:
Я знаю using
оператор, однако я не могу всегда использовать его, потому что у меня есть некоторые членские переменные, которые должны сохранить дольше, чем один вызов. Например, объекты соединения и транзакции.
Спасибо!
Я предполагаю, что это поля, а не локальные переменные, поэтому ключевое слово с использованием
не имеет смысла.
Является ли эта универсальная функция плохой идеей?
Я думаю, что это хорошая идея, и я использовал аналогичную функцию несколько раз; +1 за универсальность.
Обязательно ли устанавливать для объекта значение NULL?
Технически объект должен разрешать множественные вызовы его метода Dispose
. (Например, это происходит, если объект воскрешается во время финализации.) На практике это зависит от вас, доверяете ли вы авторам этих классов или хотите ли вы кодировать защитно. Лично я проверяю значение null, а затем устанавливаю ссылки на null.
Изменить: Если этот код находится внутри метода Dispose
вашего собственного объекта, то при невозможности установить ссылки на null не произойдет утечки памяти. Напротив, это удобно в качестве защиты от двойной утилизации.
Вам следует подумать, можно ли использовать оператор using
.
using (SqlCommand command = ...)
{
// ...
}
Это гарантирует, что Dispose
будет вызван на командном объекте, когда управление выходит из области действия using. Это имеет ряд преимуществ перед написанием кода очистки самостоятельно, как это сделали вы:
Нужно ли устанавливать объект в null?
Обычно нет необходимости устанавливать переменные в null, когда вы закончили их использовать. Главное, чтобы вы вызвали Dispose, когда закончили использовать ресурс. Если вы используете приведенный выше паттерн, то не только нет необходимости устанавливать переменную в null, но это приведет к ошибке компиляции:
Cannot assign to 'c' because it is a 'using variable'
Следует отметить, что using
работает только в том случае, если объект приобретается и утилизируется в одном и том же вызове метода. Вы не можете использовать этот паттерн, если ваши ресурсы должны оставаться живыми в течение более чем одного вызова метода. В этом случае вы, возможно, захотите, чтобы ваш класс реализовал IDisposable
и убедился, что ресурсы очищаются при вызове метода Dispose. В этом случае вам понадобится код, подобный тому, что вы написали. Установка переменных в null
не является неправильной в данном случае, но это не важно, потому что сборщик мусора все равно правильно очистит память. Главное - убедиться, что все ресурсы, которыми вы владеете, утилизируются при вызове метода dispose, и вы это делаете.
Пара деталей реализации:
ObjectDisposedException
если ваш объект уже был утилизирован. Вы должны реализовать IDisposable
в классе, которому принадлежат эти поля. См. запись в моем блоге на эту тему. Если это не работает, значит, класс не следует принципам ООП и нуждается в рефакторинге.
Нет необходимости устанавливать переменные в null
после их удаления.
Если у ваших объектов много работы по очистке, они могут захотеть отследить, что нужно удалить, в отдельном списке одноразового использования и обработать все сразу. Затем при разборке ему не нужно запоминать все, что нужно удалить (и не нужно проверять значение 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();
}
}
Учитывая, что iDisposable не включает в себя какой-либо стандартный способ определения того, был ли объект удален, я предпочитаю устанавливать значение null, когда я удаляю их. Конечно, удаление объекта, который уже был удален, безвредно, но приятно иметь возможность исследовать объект в окне просмотра и сразу сказать, какие поля были удалены. Также приятно иметь возможность иметь тест кода, чтобы гарантировать, что объекты, которые должны были быть удалены (при условии, что код придерживается соглашения об обнулении переменных при их удалении и ни в какой другой момент).
Почему вы не используете конструкцию using C #? http://msdn.microsoft.com/en-us/library/yh598w02.aspx Значение null, если не требуется.
Я собираюсь предположить, что вы создаете ресурс в одном методе, утилизируете его в другом, а используете в одном или нескольких других, что делает оператор using
бесполезным для вас.
В этом случае ваш метод совершенно нормален.
Что касается второй части вашего вопроса ("Необходимо ли устанавливать его в null?"), то простой ответ: "Нет, но это ничему не повредит".
Большинство объектов хранят один ресурс - память, освобождением которой занимается сборщик мусора, так что нам не нужно об этом беспокоиться. Некоторые из них содержат и другие ресурсы: хэндл файла, соединение с базой данных и т.д. Для второй категории мы должны реализовать IDisposable, чтобы освободить этот другой ресурс.
После вызова метода Dispose обе категории становятся одинаковыми: они удерживают память. В этом случае мы можем просто позволить переменной выйти из области видимости, сбросив ссылку на память и позволив GC освободить ее в конце концов. Или мы можем форсировать этот вопрос, установив переменную в null и явно сбросив ссылку на память. Нам все равно придется ждать, пока GC включится, чтобы память действительно была освобождена, и более чем вероятно, что переменная все равно выйдет из области видимости через несколько минут после установки ее в null, так что в подавляющем большинстве случаев это не будет иметь никакого эффекта, но в редких случаях это позволит освободить память на несколько секунд раньше.
Однако в вашем конкретном случае, когда вы проверяете наличие null, чтобы понять, следует ли вообще вызывать Dispose, вам, вероятно, следует установить его в null, если есть вероятность, что вы можете вызвать Dispose() дважды.
Другие рекомендовали использовать
конструкцию, которую я также рекомендую. Тем не менее, я хотел бы отметить, что, даже если вам нужен служебный метод, совершенно не нужно делать его универсальным так, как вы это сделали. Просто объявите свой метод, чтобы принять IDisposable
:
private static void DisposeObject( ref IDisposable disposableObject )
{
if( disposableObject != null )
{
disposableObject.Dispose();
disposableObject = null;
}
}
Вам никогда не нужно устанавливать переменные в null
. Весь смысл IDisposable.Dispose
в том, чтобы привести объект в состояние, в котором он может безвредно болтаться в памяти, пока GC не завершит его, так что вы просто "утилизируете и забываете".
Мне интересно, почему вы считаете, что нельзя использовать оператор using
. Создание объекта в одном методе и его утилизация в другом методе - это действительно большой запах кода в моей книге, потому что вы быстро теряете представление о том, что где открыто. Лучше рефакторить код следующим образом:
using(var xxx = whatever()) {
LotsOfProcessing(xxx);
EvenMoreProcessing(xxx);
NowUseItAgain(xxx);
}
Я уверен, что для этого есть стандартное название паттерна, но я называю это просто "уничтожить все, что вы создали, но ничего больше".