Должен ли я Dispose() DataSet и DataTable?
DataSet и DataTable оба реализуют IDisposable, поэтому, согласно общепринятым рекомендациям, я должен вызывать их методы Dispose().
Однако из того, что я прочитал, DataSet и DataTable на самом деле не имеют никаких неуправляемых ресурсов, поэтому Dispose() на самом деле ничего не делает.
Плюс я не могу просто использовать using(DataSet myDataSet...)
потому что DataSet имеет коллекцию DataTables.
Поэтому, чтобы быть в безопасности, мне нужно перебрать myDataSet.Tables, избавиться от каждого из DataTables, а затем избавиться от DataSet.
Итак, стоит ли вызывать Dispose() для всех моих DataSets и DataTables?
Приложение:
Для тех из вас, кто считает, что DataSet должен быть утилизирован: как правило, шаблон для утилизации должен использовать using
или же try..finally
потому что вы хотите гарантировать, что Dispose() будет вызван.
Тем не менее, это становится ужасно быстро для коллекции. Например, что вы делаете, если один из вызовов Dispose() выдал исключение? Вы проглатываете это (что является "плохим"), чтобы вы могли продолжать распоряжаться следующим элементом?
Или вы предлагаете мне просто вызвать myDataSet.Dispose() и забыть об удалении DataTables в myDataSet.Tables?
11 ответов
Вот пара обсуждений, объясняющих, почему Dispose не является необходимым для DataSet.
Распорядиться или не распорядиться?:
Метод Dispose в DataSet существует ТОЛЬКО из-за побочного эффекта наследования - другими словами, он фактически не делает ничего полезного в финализации.
Должен ли Dispose вызываться для объектов DataTable и DataSet? включает в себя некоторые объяснения от MVP:
Пространство имен system.data (ADONET) не содержит неуправляемых ресурсов. Поэтому нет необходимости избавляться от любого из них, если вы не добавили к нему что-то особенное.
Понимание метода утилизации и наборов данных? имеет комментарий от авторитета Скотта Аллена:
На практике мы редко располагаем набором данных, потому что он дает мало преимуществ "
Таким образом, все согласны с тем, что в настоящее время нет веских причин для вызова Dispose для DataSet.
Обновление (1 декабря 2009 г.):
Я хотел бы изменить этот ответ и признать, что первоначальный ответ был ошибочным.
Первоначальный анализ действительно применим к объектам, которые требуют доработки - и точка зрения, что практики не должны приниматься на поверхности без точного, глубокого понимания, все еще остается в силе.
Однако оказывается, что DataSets, DataViews, DataTables подавляют завершение в своих конструкторах - вот почему вызов Dispose() для них явно ничего не делает.
Предположительно, это происходит потому, что у них нет неуправляемых ресурсов; поэтому, несмотря на то, что MarshalByValueComponent учитывает неуправляемые ресурсы, этим конкретным реализациям нет необходимости, и поэтому они могут отказаться от завершения.
(То, что авторы.NET позаботятся о том, чтобы подавить финализацию для тех типов, которые обычно занимают больше всего памяти, говорит о важности этой практики в целом для финализуемых типов.)
Несмотря на то, что эти детали все еще недостаточно документированы с момента создания.NET Framework (почти 8 лет назад), довольно удивительно (что вы по сути оставлены на своих собственных устройствах, чтобы просеивать хотя и противоречивые, неоднозначные материалы, чтобы собрать воедино кусочки). иногда разочаровывает, но обеспечивает более полное понимание структуры, на которую мы полагаемся каждый день).
После большого чтения, вот мое понимание:
Если объект требует завершения, он может занимать память дольше, чем нужно - вот почему: a) любой тип, который определяет деструктор (или наследует от типа, который определяет деструктор), считается финализуемым; б) При выделении (до запуска конструктора) указатель помещается в очередь финализации; c) Для финализуемого объекта обычно требуется 2 коллекции (вместо стандартной 1); d) Подавление завершения не удаляет объект из очереди завершения (как сообщает!FinalizeQueue в SOS) Эта команда вводит в заблуждение; Знание того, какие объекты находятся в очереди завершения (само по себе), не помогает; Было бы полезно знать, какие объекты находятся в очереди финализации и все еще требуют завершения (есть ли команда для этого?)
Подавление финализации отключает немного в заголовке объекта, указывая среде выполнения, что ему не нужно вызывать свой Finalizer (не нужно перемещать очередь FReachable); Он остается в очереди Финализации (и продолжает сообщаться!FinalizeQueue в SOS)
Классы DataTable, DataSet, DataView имеют корни в MarshalByValueComponent, финализируемом объекте, который может (потенциально) обрабатывать неуправляемые ресурсы.
- Поскольку DataTable, DataSet, DataView не представляют неуправляемые ресурсы, они подавляют завершение в своих конструкторах
- Хотя это необычный шаблон, он освобождает вызывающего абонента от необходимости беспокоиться о вызове Dispose после использования.
- Это, а также тот факт, что DataTable потенциально могут совместно использоваться различными DataSets, вероятно, поэтому DataSets не заботятся о том, чтобы избавиться от дочерних DataTables.
- Это также означает, что эти объекты появятся под!FinalizeQueue в SOS
- Тем не менее, эти объекты должны быть восстановлены после одной коллекции, как и их не финализируемые аналоги
4 (новые ссылки):
- http://www.devnewsgroups.net/dotnetframework/t19821-finalize-queue-windbg-sos.aspx
- http://blogs.msdn.com/tom/archive/2008/04/28/asp-net-tips-looking-at-the-finalization-queue.aspx
- http://issuu.com/arifaat/docs/asp_net_3.5unleashed
- http://msdn.microsoft.com/en-us/magazine/bb985013.aspx
- http://blogs.msdn.com/tess/archive/2006/03/27/561715.aspx
Оригинальный ответ:
Есть много вводящих в заблуждение и вообще очень плохих ответов на этот вопрос - любой, кто приземлился здесь, должен игнорировать шум и внимательно прочитать ссылки ниже.
Без сомнения, Dispose должен вызываться на любых объектах Finalizable.
DataTables являются Завершаемыми.
Вызов Dispose значительно ускоряет восстановление памяти.
MarshalByValueComponent вызывает GC.SuppressFinalize(this) в своем Dispose() - пропустить это означает, что нужно ждать десятки, если не сотни коллекций Gen0, прежде чем память будет освобождена:
С этим базовым пониманием завершения мы уже можем вывести некоторые очень важные вещи:
Во-первых, объекты, которые требуют доработки, живут дольше, чем объекты, которые не нуждаются На самом деле они могут жить намного дольше. Например, предположим, что объект в gen2 должен быть завершен. Завершение будет запланировано, но объект все еще находится в gen2, поэтому он не будет повторно собран до следующей коллекции gen2. Это действительно может занять очень много времени, и, на самом деле, если дела пойдут хорошо, это будет долгое время, потому что коллекции gen2 дорогостоящие, и поэтому мы хотим, чтобы они происходили очень редко. Старые объекты, нуждающиеся в доработке, могут ждать десятки, если не сотни коллекций gen0, прежде чем освободить их пространство.
Во-вторых, объекты, которые требуют доработки, наносят сопутствующий ущерб. Поскольку внутренние указатели на объекты должны оставаться действительными, в памяти останутся не только объекты, которые непосредственно нуждаются в финализации, но и все, на что ссылается объект, прямо или косвенно, также останется в памяти. Если бы огромное дерево объектов было привязано к одному объекту, который требовал завершения, тогда все дерево задержалось бы, возможно, на долгое время, как мы только что обсуждали. Поэтому важно экономно использовать финализаторы и размещать их на объектах, которые имеют как можно меньше внутренних указателей на объекты. В приведенном мною примере дерева вы легко можете избежать проблемы, переместив ресурсы, нуждающиеся в финализации, в отдельный объект и сохранив ссылку на этот объект в корне дерева. С этим скромным изменением будет задерживаться только один объект (надеюсь, хороший маленький объект), а стоимость завершения минимизируется.
Наконец, объекты, нуждающиеся в финализации, создают работу для потока финализатора. Если ваш процесс финализации сложен, один и единственный поток финализатора будет тратить много времени на выполнение этих шагов, что может вызвать отставание в работе и, следовательно, привести к задержке большего количества объектов в ожидании завершения. Поэтому жизненно важно, чтобы финализаторы выполняли как можно меньше работы. Помните также, что хотя все указатели объектов остаются действительными во время финализации, возможно, эти указатели приводят к объектам, которые уже были завершены и, следовательно, могут быть менее чем полезными. Как правило, безопаснее избегать следования указателям объектов в коде финализации, даже если указатели действительны. Безопасный, короткий путь к коду завершения - лучший.
Возьмите его у кого-то, кто видел сотни МБ не ссылающихся на них таблиц данных в Gen2: это очень важно и совершенно пропущено ответами в этой теме.
Рекомендации:
1 - http://msdn.microsoft.com/en-us/library/ms973837.aspx
2 - http://vineetgupta.spaces.live.com/blog/cns!8DE4BDC896BEE1AD!1104.entry http://www.dotnetfunda.com/articles/article524-net-best-practice-no-2-improve-garbage-collector-performance-using-finalizedispose-pattern.aspx с помощью-finalizedispose-pattern.aspx
3 - http://codeidol.com/csharp/net-framework/Inside-the-CLR/Automatic-Memory-Management/
Вы должны предположить, что он делает что-то полезное, и вызывать Dispose, даже если он ничего не делает в текущем. Воплощения NET Framework, нет никаких гарантий, что так будет и в будущих версиях, что приведет к неэффективному использованию ресурсов.
Даже если у объекта нет неуправляемых ресурсов, утилизация может помочь GC, разбивая графы объектов. В общем, если объект реализует IDisposable, тогда должен быть вызван Dispose().
Делает ли Dispose() что-то или нет, зависит от данного класса. В случае DataSet реализация Dispose() наследуется от MarshalByValueComponent. Он удаляет себя из контейнера и вызывает событие Disposed. Исходный код ниже (разобран с помощью.NET Reflector):
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
lock (this)
{
if ((this.site != null) && (this.site.Container != null))
{
this.site.Container.Remove(this);
}
if (this.events != null)
{
EventHandler handler = (EventHandler) this.events[EventDisposed];
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
}
}
Вы сами создаете таблицы данных? Потому что перебирать дочерние элементы любого объекта (как в DataSet.Tables) обычно не нужно, так как задача родителя - распоряжаться всеми дочерними элементами.
Обычно правило таково: если вы создали его, и он реализует IDisposable, утилизируйте его. Если вы НЕ создали его, то НЕ утилизируйте его, это работа родительского объекта. Но у каждого объекта могут быть свои особые правила, проверьте документацию.
Для.net 3.5 он явно говорит: "Утилизируйте его, когда он больше не используется", так что я бы так и сделал.
Я вызываю dispose в любое время, когда объект реализует IDisposeable. Это там по причине.
DataSets может быть огромным занятием памяти. Чем раньше они могут быть помечены для очистки, тем лучше.
Обновить
Прошло 5 лет с тех пор, как я ответил на этот вопрос. Я все еще согласен с моим ответом. Если есть метод dispose, его следует вызывать, когда вы закончите с объектом. Интерфейс IDispose был реализован по причине.
Если ваше намерение или контекст этого вопроса на самом деле является сборкой мусора, то вы можете установить для наборов данных и таблиц данных значение null в явном виде или использовать ключевое слово using и позволить им выйти из области видимости. Утилизация не так много, как тетранейтрон сказал это ранее. GC будет собирать объекты набора данных, на которые больше нет ссылок, а также те, которые находятся вне области видимости.
Я действительно хочу, чтобы НАСТОЯЩИЕ люди заставляли голосовать, чтобы они действительно написали комментарий, прежде чем опровергнуть ответ.
Наборы данных реализуют IDisposable основательный MarshalByValueComponent, который реализует IDisposable. Так как наборы данных управляются, нет никакой реальной выгоды от вызова утилизации.
Нет необходимости в Dispose(), поскольку DataSet наследует класс MarshalByValueComponent, а MarshalByValueComponent реализует интерфейс IDisposable
И это может быть лучший / правильный способ утилизации и освобождения памяти, потребляемой
DataSet
.
try
{
DataSet ds = new DataSet("DS");
//use table DataTable here
}
catch { }
finally
{
if (ds != null)
{
ds.EnforceConstraints = false;
ds.Relations.Clear();
int totalCount = ds.Tables.Count;
for (int i = totalCount - 1; i >= 0; i--)
{
DataTable td1 = ds.Tables[i];
if (td1 != null)
{
td1.Constraints.Clear();
td1.Clear();
td1.Dispose();
td1 = null;
}
}
ds.Tables.Clear();
ds.Dispose();
ds = null;
}
}
Попробуйте использовать функцию Clear(). Это прекрасно работает для меня для утилизации.
DataTable dt = GetDataSchema();
//populate dt, do whatever...
dt.Clear();
Это правильный способ правильно утилизировать
DataTable
.
private DataTable CreateSchema_Table()
{
DataTable td = null;
try
{
td = new DataTable();
//use table DataTable here
return td.Copy();
}
catch { }
finally
{
if (td != null)
{
td.Constraints.Clear();
td.Clear();
td.Dispose();
td = null;
}
}
}
Прежде всего, я бы проверил, что Dispose делает с DataSet. Возможно использование отражателя от redgate поможет.