Действительно ли RAII безопасно использовать в C#? И другие собирающие "мусор" языки?

Мой фаворит - это:

// what does this do?
x = x++;

Для ответа на некоторые комментарии это - неопределенное поведение согласно стандарту. Видя это, компилятору позволяют сделать что-либо до и включая формат Ваш жесткий диск. Посмотрите, например этот комментарий здесь . Точка не то, что Вы видите, что существует возможное разумное ожидание некоторого поведения. Из-за стандарта C++ и пути определяются точки последовательности, эта строка кода является на самом деле неопределенным поведением.

, Например, если бы мы имели x = 1 перед строкой выше, тогда чем допустимое закончилось бы быть впоследствии? Кто-то прокомментировал, что это должно быть

, x увеличен 1

, таким образом, мы должны видеть x == 2 впоследствии. Однако это не на самом деле верно, Вы найдете некоторые компиляторы, которые имеют x == 1 впоследствии, или возможно даже x == 3. Необходимо было бы пристально смотреть на сгенерированный блок для наблюдения, почему это могло бы быть, но различия происходят из-за базовой проблемы. По существу я думаю, что это вызвано тем, что компилятору позволяют оценить эти два оператора присвоений в любом порядке, который он любит, таким образом, он мог сделать x++ первый, или x = сначала.

10
задан Net Citizen 18 November 2009 в 16:08
поделиться

4 ответа

Это очень, очень плохая идея.

Финализаторы не вызываются, когда переменные выходят за пределы области видимости. Они вызываются в какой-то момент до того, как объект будет собран сборщиком мусора, что может произойти намного позже.

Вместо этого вы хотите реализовать IDisposable , а затем вызывающие абоненты могут использовать:

using (YourClass yc = new YourClass())
{
    // Use yc in here
}

Это будет вызовите Dispose автоматически.

Финализаторы очень редко необходимы в C # - они требуются только тогда, когда вы напрямую владеете неуправляемым ресурсом (например, дескриптором Windows). В противном случае у вас обычно есть некоторый управляемый класс-оболочка (например, FileStream ), у которого будет финализатор, если он нужен.

Обратите внимание, что вам понадобится только любой из этого, когда у вас есть ресурсы для очистки - большинство классов в .NET не t реализовать IDisposable .

РЕДАКТИРОВАТЬ: просто чтобы ответить на комментарий о вложенности, я согласен, это может быть немного некрасиво, но вам не нужно с использованием операторов очень часто по моему опыту. Вы также можете вложить использование по вертикали, как это, в случае, если у вас есть два непосредственно смежных:

using (TextReader reader = File.OpenText("file1.txt"))
using (TextWriter writer = File.CreateText("file2.txt"))
{
    ...
}
20
ответ дан 3 December 2019 в 13:56
поделиться

Редактировать: Ясно, что Эрик и я не согласны с этим использованием. : o

Вы можете использовать что-то вроде этого:

public sealed class CursorApplier : IDisposable
{
    private Control _control;
    private Cursor _previous;

    public CursorApplier(Control control, Cursor cursor)
    {
        _control = control;
        _previous = _control.Cursor;
        ApplyCursor(cursor);
    }

    public void Dispose()
    {
        ApplyCursor(_previous);
    }

    private void ApplyCursor(Cursor cursor)
    {
        if (_control.Disposing || _control.IsDisposed)
            return;

        if (_control.InvokeRequired)
            _control.Invoke(new MethodInvoker(() => _control.Cursor = cursor));
        else
            _control.Cursor = cursor;
    }
}

// then...

using (new CursorApplier(control, Cursors.WaitCursor))
{
    // some work here
}
6
ответ дан 3 December 2019 в 13:56
поделиться

Используйте шаблон IDisposable, если вы хотите сделать RAII-подобные вещи в C #

1
ответ дан 3 December 2019 в 13:56
поделиться

Вы знаете, многие умные люди говорят «используйте IDisposable, если вы хотите реализовать RAII на C #», и я просто не куплюсь на это. Я знаю, что меня здесь меньшинство, но когда я вижу "using (blah) {foo (blah);}", я автоматически думаю, что "blah содержит неуправляемый ресурс, который необходимо агрессивно очистить, как только будет выполнено foo ( или бросает), чтобы кто-то другой мог использовать этот ресурс ". Я не думаю, что "blah не содержит ничего интересного, но есть какая-то семантически важная мутация, которая должна произойти, и мы собираемся представить эту семантически важную операцию символом '}'" - некоторые мутации, подобные некоторым стек должен быть снят, или какой-то флаг должен быть сброшен, или что-то еще.

Я говорю, что если у вас есть семантически важная операция, которую нужно выполнить, когда что-то завершается, у нас есть слово для этого, и слово «наконец». Если операция важна , то она должна быть представлена ​​как оператор, который вы можете увидеть прямо здесь и поставить точку останова, а не как невидимый побочный эффект правой фигурной скобки.

Итак, давайте подумайте о вашей конкретной операции. Вы хотите представить:

var oldPosition = form.CursorPosition;
form.CursorPosition = newPosition;
blah;
blah;
blah;
form.CursorPosition = oldPosition;

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

Теперь у вас есть точка принятия решения. Что если мля выкинет? Если бла выкидывает, то произошло нечто непредвиденное . Теперь вы не имеете ни малейшего представления, в каком состоянии находится «форма»; это мог быть код в " с которой, возможно, кто-то умел обращаться, потеряно. Вы уничтожили информацию об исходной причине проблемы, которая могла быть полезной. И вы заменили его другой информацией об ошибке перемещения курсора, которая никому не нужна.

3) Напишите сложную логику, которая обрабатывает проблемы (2) - перехватить исключение, а затем попытаться сбросить позицию курсора в отдельном блоке try-catch-finally, который подавляет новое исключение и повторно генерирует исходное. исключение. Это может быть сложно сделать правильно, но вы можете это сделать.

Честно говоря, я считаю, что правильный ответ почти всегда (1). Если что-то ужасное пошло не так, то вы не можете спокойно рассуждать о том, какие дальнейшие мутации до хрупкого состояния являются законными. Если вы не знаете, как обработать исключение, ОТКАЗАТЬСЯ.

(2) - это решение, которое дает RAII с блоком using. Опять же, причина, по которой мы используем блоки в первую очередь, состоит в том, чтобы агрессивно очищать жизненно важные ресурсы, когда они больше не могут быть использованы. Произошло ли исключение или нет, эти ресурсы необходимо быстро очистить , поэтому блоки «using» имеют семантику «finally». Но семантика «finally» не обязательно подходит для операций, которые не являются операциями по очистке ресурсов; как мы видели, блоки finally с программной семантикой неявно предполагают, что очистка всегда безопасна; но тот факт, что мы находимся в ситуации обработки исключений, указывает на то, что это может быть небезопасно.

(3) звучит как большая работа.

Короче говоря, я говорю: хватит стараться быть таким умным. Вы хотите изменить курсор, выполнить некоторую работу и включить курсор. Итак, напишите три строки кода, которые сделают это, готово. Никаких костюмов RAII не требуется; он просто добавляет ненужную косвенность, затрудняет чтение программы и делает ее потенциально более хрупкой в ​​исключительных ситуациях, а не менее хрупкой.

12
ответ дан 3 December 2019 в 13:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: