У меня есть класс C# с функцией Dispose через IDisposable. Он предназначен для использования внутри блока using, поэтому дорогостоящий ресурс, который он обрабатывает, может быть освобожден сразу.
Проблема в том, что ошибка возникла, когда исключение было сгенерировано до вызова Dispose, а программист не использовал using или finally.
В C++ мне никогда не приходилось об этом беспокоиться. Вызов деструктора класса будет автоматически вставлен в конец области видимости объекта. Единственный способ избежать этого - использовать оператор new и удерживать объект за указателем, но это не требует от программиста дополнительной работы, которую он сделал бы случайно, например, если бы он забыл использовать using.
Есть ли способ для автоматического использования блока using в C#?
Большое спасибо.
ОБНОВИТЬ:
Я хотел бы объяснить, почему я не принимаю ответы финализатора. Эти ответы технически верны сами по себе, но они не являются деструкторами стиля C++.
Вот ошибка, которую я обнаружил, сведенная к основному ...
try
{
PleaseDisposeMe a = new PleaseDisposeMe();
throw new Exception();
a.Dispose();
}
catch (Exception ex)
{
Log(ex);
}
// This next call will throw a time-out exception unless the GC
// runs a.Dispose in time.
PleaseDisposeMe b = new PleaseDisposeMe();
Использование FXCop - отличное предложение, но если бы это был мой единственный ответ, мой вопрос должен был бы стать просьбой к людям C# или использовать C++. Двадцать вложенных операторов using?





~ClassName()
{
}
РЕДАКТИРОВАТЬ (жирный шрифт):
Если будет вызван, когда объект будет перемещен из области видимости и убран сборщиком мусора однако это не детерминировано и не гарантируется, что произойдет в какой-либо конкретный момент.. Это называется финализатором. Все объекты с финализатором помещаются в специальную очередь финализации сборщиком мусора, где для них вызывается метод finalize (так что технически объявление пустых финализаторов снижает производительность).
«Принятый» шаблон удаления в соответствии с рекомендациями по платформе выглядит следующим образом с неуправляемыми ресурсами:
public class DisposableFinalisableClass : IDisposable
{
~DisposableFinalisableClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// tidy managed resources
}
// tidy unmanaged resources
}
}
Таким образом, приведенное выше означает, что если кто-то вызывает Dispose, неуправляемые ресурсы приводятся в порядок. Однако в случае, если кто-то забыл вызвать Dispose или исключение, препятствующее вызову Dispose, неуправляемые ресурсы все равно будут убраны, только немного позже, когда GC получит на нем свои грязные перчатки (включая закрытие приложения или неожиданное завершение ).
К сожалению, это невозможно сделать прямо в коде. Если это проблема внутри компании, существуют различные решения для анализа кода, которые могут выявить подобные проблемы. Вы смотрели на FxCop? Я думаю, что это отловит эти ситуации и во всех случаях, когда объекты IDisposable могут остаться висящими. Если это компонент, который люди используют за пределами вашей организации, и вам не может потребоваться FxCop, тогда документация - ваш единственный выход :).
Обновлено: в случае финализаторов это на самом деле не гарантирует, когда произойдет финализация. Так что это может быть решением для вас, но это зависит от ситуации.
Это ничем не отличается от того, что программист забывает использовать Удалить в C++, за исключением того, что, по крайней мере, здесь сборщик мусора все равно его догонит.
И вам никогда не нужно использовать IDisposable, если единственный ресурс, о котором вы беспокоитесь, - это память. Фреймворк справится с этим самостоятельно. IDisposable предназначен только для неуправляемых ресурсов, таких как соединения с базой данных, файловые потоки, сокеты и т. д.
В этом случае вы утверждаете, что события попадают в категорию «неуправляемых».
Joel-> Wrong - Если у вас есть неуправляемый объект, который требует или использует память, вы должны быть осторожны с шаблоном удаления и использованием давления памяти. Игнорирование этого приведет либо к: неуправляемому ресурсу потребляется память, которую хочет gc, либо к сбою неуправляемого выделения из-за того, что сборщик мусора поглощает память.
@Daniel Earwicker: В этом случае вам, вероятно, следует сделать свой конструктор закрытым и передать по ссылке копию строящегося объекта. Вызовите свой конструктор из фабричного метода, который может вызывать dispose для вашего объекта, если конструктор выдает ошибку (помня, что объект может быть построен только частично).
@ supercat - Исходя из этого, может показаться, что каждый метод, изменяющий состояние, должен быть приватным и быть обернут отдельным публичным методом, который улавливает любые генерируемые им исключения и отменяет любые частичные изменения состояния. Итак, почему бы не поместить эту обработку внутри самого метода, где это имеет наибольший смысл?
Также как "передача по ссылке копии строящегося объекта" поможет, если я участвую в событиях? Скопированный объект получит события.
@Daniel Earwicker: Извините, я неправильно истолковал конструктор как инициализатор; в vb.net инициализаторы производного класса выполняются между конструктором базового уровня и конструктором производного класса и могут перехватывать события. Это может быть нормально, если объекты, чьи события перехватываются, полностью содержатся в строящемся классе; в противном случае единственный известный мне способ избежать утечки событий - это обернуть конструктор.
@Daniel Earwicker: В vb.net можно спроектировать класс таким образом, чтобы IDisposables можно было безопасно выделять даже в инициализаторах полей, но только если конструктор заключен в оболочку. В vb.net, если конструктор базового класса создает IDisposable, а инициализатор производного класса выбрасывает, единственный способ детерминированно позаботиться о базовых IDisposables - обернуть конструктор. В C# включение кода каждого конструктора в try-catch, который удаляет частично созданный объект, вероятно, настолько хорош, насколько это возможно; это необходимо делать в производных классах, чтобы избежать утечки базовых объектов.
@Quarrelsome
If will get called when the object is moved out of scope and is tidied by the garbage collector.
Это утверждение вводит в заблуждение, и как я его прочитал, неверно: нет абсолютно никакой гарантии, когда будет вызван финализатор. Вы абсолютно правы, что в billpg должен быть реализован финализатор; однако он не будет вызываться автоматически, когда объект выходит из области видимости, как он того хочет. Свидетельство, первый пункт под Операции завершения имеют следующие ограничения.
Фактически Microsoft предоставила Крису Селлсу грант на создание реализации .NET, которая использовала бы подсчет ссылок вместо сборки мусора Связь. Как оказалось, был удар по производительности значительный.
Лучше всего заставить этот класс самостоятельно высвобождать дорогостоящий ресурс, прежде чем он будет удален.
Например, если это соединение с базой данных, подключайтесь только при необходимости и немедленно выпускайте, задолго до того, как фактический класс будет удален.
Где я работаю, мы руководствуемся следующими принципами:
Чтобы облегчить нашу жизнь, у нас также есть метод SafeDispose в нашей инфраструктуре, который на всякий случай вызывает метод Dispose своего аргумента в блоке try-catch (с ведением журнала ошибок) (хотя методы Dispose не должны вызывать исключения. ).
См. Также: предложения Крис Лайон относительно IDisposable
Редактировать: @Quarrelsome: Вам следует вызвать GC.SuppressFinalize внутри 'Dispose', чтобы, если объект был удален, он не был "повторно удален".
Также обычно рекомендуется держать флаг, указывающий, был ли объект уже удален или нет. Обычно бывает неплохо выглядит следующий паттерн:
class MyDisposable: IDisposable {
public void Dispose() {
lock(this) {
if (disposed) {
return;
}
disposed = true;
}
GC.SuppressFinalize(this);
// Do actual disposing here ...
}
private bool disposed = false;
}
Конечно, блокировка не всегда необходима, но если вы не уверены, будет ли ваш класс использоваться в многопоточной среде или нет, рекомендуется сохранить ее.
Добавление финализаторов к каждому объекту, реализующему IDisposable, только для того, чтобы вы могли регистрировать, когда финализатор был вызван, не является хорошей идеей. Финализаторы увеличивают стоимость вашего объекта (он переживает дополнительный цикл сборки мусора), и большинство предположений, которые вы можете сделать о внутреннем состоянии, недействительны.
«Где я работаю, мы руководствуемся следующими принципами: у каждого класса IDisposable должен быть финализатор ...» Измените свои правила.
Мне не нравится, когда классы с финализаторами содержат ссылки на другие объекты, даже если финализаторы не должны запускаться. Я бы подумал, что было бы лучше определить класс FinalizationSquawker, который будет кричать, если он будет завершен, и классы, которые необходимо удалить, создают FinalizationSquawker при инициализации и удаляют его, когда они удаляются. Кстати, я бы предложил использовать Interlocked.Exchange для удаленного флага, а не использовать блокировку.
Лучше всего использовать финализатор в вашем классе и всегда использовать блоки using.
На самом деле нет прямого эквивалента, финализаторы выглядят как деструкторы C, но ведут себя по-другому.
Вы должны вкладывать блоки using, поэтому по умолчанию макет кода C# помещает их в одну строку ...
using (SqlConnection con = new SqlConnection("DB con str") )
using (SqlCommand com = new SqlCommand( con, "sql query") )
{
//now code is indented one level
//technically we're nested twice
}
Когда вы не используете using, вы все равно можете делать то, что он делает под капотом:
PleaseDisposeMe a;
try
{
a = new PleaseDisposeMe();
throw new Exception();
}
catch (Exception ex) { Log(ex); }
finally {
//this always executes, even with the exception
a.Dispose();
}
С управляемым кодом C# очень хорошо заботится о своей собственной памяти, даже когда что-то плохо расположено. Если вы часто имеете дело с неуправляемыми ресурсами, это не так уж и важно.
«IDisposable предназначен только для неуправляемых ресурсов ...» Что, если я напишу класс, который подписывается на событие в своем конструкторе? Мне нужно соответствующее место, где я могу отказаться от подписки, а клиентам нужен стандартный способ попросить меня убрать такие вещи. В противном случае мой объект может оставаться в живых бесконечно долго.