Как сделать деструкторы стиля C ++ в C #? - PullRequest
2 голосов
/ 06 сентября 2008

У меня есть класс C # с функцией Dispose через IDisposable. Он предназначен для использования внутри блока using, поэтому дорогой ресурс, который он обрабатывает, можно сразу же освободить.

Проблема заключается в том, что произошла ошибка, когда возникла исключительная ситуация до вызова Dispose, и программист забыл использовать using или finally.

В C ++ мне никогда не приходилось беспокоиться об этом. Вызов деструктора класса будет автоматически вставлен в конец области видимости объекта. Единственный способ избежать этого - использовать оператор new и удерживать объект за указателем, но это требует от программиста дополнительной работы - это не то, что они сделали бы случайно, например, забыли использовать using.

Есть ли способ автоматического использования блока using в C #?

Большое спасибо.

UPDATE:

Я хотел бы объяснить, почему я не принимаю ответы финализатора. Эти ответы технически правильны сами по себе, но они не являются деструкторами стиля 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 ++. Двадцать вложенных с помощью операторов кто-нибудь?

Ответы [ 7 ]

6 голосов
/ 06 сентября 2008

Там, где я работаю, мы используем следующие рекомендации:

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

Чтобы сделать нашу жизнь проще, у нас также есть метод SafeDispose в нашей инфраструктуре, который вызывает метод Dispose для своего аргумента в блоке try-catch (с ведением журнала ошибок), на всякий случай (хотя методы Dispose не допускаются бросать исключения).

См. Также: Предложения Криса Лиона относительно IDisposable

Edit: @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;
}

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

3 голосов
/ 06 сентября 2008

К сожалению, нет никакого способа сделать это непосредственно в коде. Если это внутренняя проблема, существуют различные решения для анализа кода, которые могут решить подобные проблемы. Вы смотрели в FxCop? Я думаю, что это поймает эти ситуации и во всех случаях, когда IDisposable объекты могут быть оставлены висеть. Если это компонент, который люди используют за пределами вашей организации, и вы не можете требовать FxCop, то документация - действительно ваш единственный выход:).

Редактировать: В случае финализаторов это не гарантирует, когда завершится финализация. Так что это может быть решением для вас, но это зависит от ситуации.

2 голосов
/ 07 сентября 2008

Лучше всего использовать финализатор в своем классе и всегда использовать using блоки.

Хотя прямого эквивалента нет, финализаторы выглядят как деструкторы Си, но ведут себя по-разному.

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

2 голосов
/ 06 сентября 2008

@ Quarrelsome

If будет вызываться, когда объект выходит из области видимости и очищается сборщиком мусора.

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

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

2 голосов
/ 06 сентября 2008
~ClassName()
{
}

РЕДАКТИРОВАТЬ (жирный):

If будет вызываться, когда объект выходит из области видимости и очищается сборщиком мусора , однако это не является детерминированным и не гарантируется, что это произойдет в любой конкретный момент времени . Это называется финализатором. Все объекты с финализатором помещаются в специальную очередь финализации сборщиком мусора, где к ним вызывается метод финализации (так что технически это снижение производительности для объявления пустых финализаторов).

«Принятый» шаблон утилизации в соответствии с Основными принципами выглядит следующим образом с неуправляемыми ресурсами:

    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 получит свои грязные перчатки (включая закрытие приложения или неожиданное завершение ).

0 голосов
/ 06 сентября 2008

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

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

0 голосов
/ 06 сентября 2008

Это ничем не отличается от того, что программист забывает использовать delete в C ++, за исключением того, что, по крайней мере, здесь сборщик мусора все равно в конечном итоге его догонит.

И вам никогда не нужно использовать IDisposable, если единственный ресурс, который вас беспокоит, это память. Фреймворк справится с этим самостоятельно. IDisposable предназначен только для неуправляемых ресурсов, таких как соединения с базой данных, файловые потоки, сокеты и т. П.

...