Является ли RAII безопасным для использования в С#? И другие языки сбора мусора?

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

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

Ответ 1

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

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

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

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

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

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

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

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

using (TextReader reader = File.OpenText("file1.txt"))
using (TextWriter writer = File.CreateText("file2.txt"))
{
    ...
}

Ответ 2

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

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

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

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

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

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

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

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

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

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

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

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

(3) звучит как много работы.

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

Ответ 3

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

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

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
}

Ответ 4

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