Как сделать деструкторы стиля C++ в C#?

У меня есть класс 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?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
2 026
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

~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 предназначен только для неуправляемых ресурсов, таких как соединения с базой данных, файловые потоки, сокеты и т. д.

«IDisposable предназначен только для неуправляемых ресурсов ...» Что, если я напишу класс, который подписывается на событие в своем конструкторе? Мне нужно соответствующее место, где я могу отказаться от подписки, а клиентам нужен стандартный способ попросить меня убрать такие вещи. В противном случае мой объект может оставаться в живых бесконечно долго.

Daniel Earwicker 23.10.2008 02:27

В этом случае вы утверждаете, что события попадают в категорию «неуправляемых».

Joel Coehoorn 23.10.2008 17:12

Joel-> Wrong - Если у вас есть неуправляемый объект, который требует или использует память, вы должны быть осторожны с шаблоном удаления и использованием давления памяти. Игнорирование этого приведет либо к: неуправляемому ресурсу потребляется память, которую хочет gc, либо к сбою неуправляемого выделения из-за того, что сборщик мусора поглощает память.

morechilli 17.02.2009 21:08

@Daniel Earwicker: В этом случае вам, вероятно, следует сделать свой конструктор закрытым и передать по ссылке копию строящегося объекта. Вызовите свой конструктор из фабричного метода, который может вызывать dispose для вашего объекта, если конструктор выдает ошибку (помня, что объект может быть построен только частично).

supercat 02.12.2010 10:50

@ supercat - Исходя из этого, может показаться, что каждый метод, изменяющий состояние, должен быть приватным и быть обернут отдельным публичным методом, который улавливает любые генерируемые им исключения и отменяет любые частичные изменения состояния. Итак, почему бы не поместить эту обработку внутри самого метода, где это имеет наибольший смысл?

Daniel Earwicker 02.12.2010 13:23

Также как "передача по ссылке копии строящегося объекта" поможет, если я участвую в событиях? Скопированный объект получит события.

Daniel Earwicker 02.12.2010 13:23

@Daniel Earwicker: Извините, я неправильно истолковал конструктор как инициализатор; в vb.net инициализаторы производного класса выполняются между конструктором базового уровня и конструктором производного класса и могут перехватывать события. Это может быть нормально, если объекты, чьи события перехватываются, полностью содержатся в строящемся классе; в противном случае единственный известный мне способ избежать утечки событий - это обернуть конструктор.

supercat 02.12.2010 17:18

@Daniel Earwicker: В vb.net можно спроектировать класс таким образом, чтобы IDisposables можно было безопасно выделять даже в инициализаторах полей, но только если конструктор заключен в оболочку. В vb.net, если конструктор базового класса создает IDisposable, а инициализатор производного класса выбрасывает, единственный способ детерминированно позаботиться о базовых IDisposables - обернуть конструктор. В C# включение кода каждого конструктора в try-catch, который удаляет частично созданный объект, вероятно, настолько хорош, насколько это возможно; это необходимо делать в производных классах, чтобы избежать утечки базовых объектов.

supercat 02.12.2010 19:02

@Quarrelsome

If will get called when the object is moved out of scope and is tidied by the garbage collector.

Это утверждение вводит в заблуждение, и как я его прочитал, неверно: нет абсолютно никакой гарантии, когда будет вызван финализатор. Вы абсолютно правы, что в billpg должен быть реализован финализатор; однако он не будет вызываться автоматически, когда объект выходит из области видимости, как он того хочет. Свидетельство, первый пункт под Операции завершения имеют следующие ограничения.

Фактически Microsoft предоставила Крису Селлсу грант на создание реализации .NET, которая использовала бы подсчет ссылок вместо сборки мусора Связь. Как оказалось, был удар по производительности значительный.

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

Например, если это соединение с базой данных, подключайтесь только при необходимости и немедленно выпускайте, задолго до того, как фактический класс будет удален.

Где я работаю, мы руководствуемся следующими принципами:

  • Каждый класс IDisposable должен имеет финализатор
  • Всякий раз, когда используется объект IDisposable, он должен использоваться внутри блока using. Единственное исключение - если объект является членом другого класса, и в этом случае содержащий класс должен быть IDisposable и должен вызывать метод элемента Dispose в своей собственной реализации Dispose. Это означает, что разработчик никогда не должен вызывать Dispose, кроме как внутри другого метода Dispose, что устраняет ошибку, описанную в вопросе.
  • Код в каждом финализаторе должен начинаться с журнала предупреждений / ошибок, уведомляющего нас о том, что финализатор был вызван. Таким образом, у вас есть очень хорошие шансы обнаружить такие ошибки, как описано выше, перед выпуском кода, а также это может быть намеком на ошибки, возникающие в вашей системе.

Чтобы облегчить нашу жизнь, у нас также есть метод 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, только для того, чтобы вы могли регистрировать, когда финализатор был вызван, не является хорошей идеей. Финализаторы увеличивают стоимость вашего объекта (он переживает дополнительный цикл сборки мусора), и большинство предположений, которые вы можете сделать о внутреннем состоянии, недействительны.

Scott Dorman 17.10.2008 13:02

«Где я работаю, мы руководствуемся следующими принципами: у каждого класса IDisposable должен быть финализатор ...» Измените свои правила.

Daniel Earwicker 23.10.2008 02:21

Мне не нравится, когда классы с финализаторами содержат ссылки на другие объекты, даже если финализаторы не должны запускаться. Я бы подумал, что было бы лучше определить класс FinalizationSquawker, который будет кричать, если он будет завершен, и классы, которые необходимо удалить, создают FinalizationSquawker при инициализации и удаляют его, когда они удаляются. Кстати, я бы предложил использовать Interlocked.Exchange для удаленного флага, а не использовать блокировку.

supercat 10.03.2011 00:17

Лучше всего использовать финализатор в вашем классе и всегда использовать блоки 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# очень хорошо заботится о своей собственной памяти, даже когда что-то плохо расположено. Если вы часто имеете дело с неуправляемыми ресурсами, это не так уж и важно.

Другие вопросы по теме