RAII безопасно использовать в C #? И другие языки сбора мусора? - PullRequest
10 голосов
/ 18 ноября 2009

Я делал класс RAII, который принимает элемент управления System.Windows.Form и устанавливает его курсор. И в деструкторе он устанавливает курсор обратно к тому, что он был.

Но разве это плохая идея? Могу ли я смело полагать, что деструктор будет вызываться, когда объекты этого класса выходят из области видимости?

Ответы [ 4 ]

20 голосов
/ 18 ноября 2009

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

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

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

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

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

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

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

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

using (TextReader reader = File.OpenText("file1.txt"))
using (TextWriter writer = File.CreateText("file2.txt"))
{
    ...
}
12 голосов
/ 18 ноября 2009

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

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

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

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

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

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

Учитывая эту ситуацию, что вы хотите сделать?

1) Решить проблему кому-либо еще. Ничего не делать. Надеюсь, что кто-то еще в стеке вызовов знает, как справиться с этой ситуацией. Причина в том, что форма уже в плохом состоянии; тот факт, что его курсор находится не в нужном месте, меньше всего беспокоит. Не тыкайте в то, что уже настолько хрупко, об этом сообщается однажды.

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

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

Честно говоря, я считаю, что правильный ответ почти всегда (1). Если что-то ужасное пошло не так, то вы не можете смело рассуждать о том, какие дальнейшие мутации в хрупкое состояние законны. Если вы не знаете, как обработать исключение, тогда GIVE UP.

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

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

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

6 голосов
/ 18 ноября 2009

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

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

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
}
1 голос
/ 18 ноября 2009

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

...