Почему в .NET нет RAII?

Будучи в первую очередь разработчиком С++, отсутствие RAII (Инициализация приобретения ресурсов) в Java и .NET всегда беспокоило меня. Тот факт, что бремя уборки переносится от автора класса к его потребителю (с помощью try finally или .NET using construct), кажется, заметно уступает.

Я вижу, почему в Java нет поддержки для RAII, поскольку все объекты находятся в куче, а сборщик мусора по сути не поддерживает детерминированное уничтожение, а в .NET с введением типов значений (struct) у нас есть (по-видимому) идеальный кандидат для RAII. Тип значения, созданный в стеке, имеет четко определенную область и можно использовать семантику деструктора С++. Однако CLR не допускает, чтобы тип значения имел деструктор.

Мои случайные поиски обнаружили один аргумент, что если тип значения в штучной упаковке, он подпадает под юрисдикцию сборщика мусора, и поэтому его уничтожение становится недетерминирована. Я считаю, что этот аргумент недостаточно силен, преимущества RAII достаточно велики, чтобы сказать, что тип значения с деструктором не может быть помещен в коробку (или использоваться как член класса).

Чтобы сократить длинный рассказ, мой вопрос: существуют ли какие-либо другие типы значений причин, которые нельзя использовать для внедрения RAII в .NET? (или вы считаете, что мой аргумент о очевидных преимуществах RAII ошибочен?)

Изменить: Я, должно быть, не сформулировал вопрос четко, так как первые четыре ответа пропустили точку. Я знаю о Finalize и его недетерминированных характеристиках, я знаю о конструкции using, и я чувствую, что эти два варианта уступают RAII. using - еще одна вещь, которую должен помнить потребитель класса (сколько людей забыли поставить StreamReader в блок using?). Мой вопрос - философский вопрос о дизайне языка, почему он так и может быть улучшен?

Например, с помощью генерического детерминистически разрушаемого типа значений я могу сделать ключевые слова using и lock избыточными (достижимыми классами библиотек):

    public struct Disposer<T> where T : IDisposable
    {
        T val;
        public Disposer(T t) { val = t; }
        public T Value { get { return val; } }
        ~Disposer()  // Currently illegal 
        {
            if (val != default(T))
                val.Dispose();
        }
    }

Я не могу не кончить цитатой, которую я когда-то видел, но не могу найти ее происхождения.

Вы можете принять мое детерминированное разрушение, когда моя холодная мертвая рука выходит из сферы действия. --Anon

Ответ 1

Лучшим заголовком будет "Почему нет RAII в С#/VB". С++/CLI (эволюция абортов, управляемая С++) имеет RAII в том же смысле, что и С++. Это всего лишь синтаксис сахара для того же шаблона финализации, что и остальные языки CLI (деструкторы в управляемых объектах для С++/CLI являются фактически финалистами), но он есть.

Вам может понравиться http://blogs.msdn.com/hsutter/archive/2004/07/31/203137.aspx

Ответ 2

Отличный вопрос и тот, который меня очень беспокоил. Похоже, что преимущества RAII воспринимаются очень по-разному. По моему опыту с .NET, недостаток детерминированной (или, по крайней мере, надежной) коллекции ресурсов является одним из основных недостатков. Фактически,.NET заставил меня несколько раз использовать целые архитектуры для работы с неуправляемыми ресурсами, которые могут (но не обязательно) требовать явного сбора. Это, конечно, огромный недостаток, потому что это усложняет общую архитектуру и направляет внимание клиента на более центральные аспекты.

Ответ 3

У Брайана Гарри есть хорошая статья об обоснованиях здесь.

Вот выдержка:

Как насчет детерминированной финализации и типов значений (structs)?

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

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

(2) конструкторы копирования - одно место где было бы действительно приятно стек выделенных мест. Они будут доведено до метода, и все будет Великий. К сожалению, чтобы получить это действительно работает, вам также нужно добавить конструкторы копирования и вызвать их каждый раз, когда экземпляр копируется. Это один из самых уродливых и наиболее сложные вещи о С++. Вы в конечном итоге получение кода, выполняемого во всем где вы этого не ожидаете. Это вызывает пучки языковых проблем. Некоторые языковые дизайнеры выбрали избегайте этого.

Скажем, мы создали структуры с деструкторов, но добавил кучу ограничения для их поведения разумным перед лицом проблем выше. Ограничения будут что-то вроде:

(1) Вы можете объявить их только как локальные переменные.

(2) Вы можете передавать только их по-исх

(3) Вы не можете назначить их, вы может обращаться только к полям и звонить методы на них.

(4) Вы не можете вставить их.

(5) Проблемы, связанные с их использованием Отражение (позднее связывание), потому что обычно включает в себя бокс.

может быть больше, но это хорошее начало.

Какими будут эти вещи? Бы вы фактически создаете файл или класс подключения к базе данных, который может ТОЛЬКО используется в качестве локальной переменной? я не верьте, что кто-то действительно будет. Вместо этого вы должны создать соединение общего назначения, а затем создать автоматическую разрушенную оболочку для использовать как локализованную локальную переменную. вызывающий мог выбрать то, что они хотел использовать. Обратите внимание, что вызывающий решение, и это не совсем инкапсулированный в самом объекте. Учитывая, что вы можете что-то использовать как предложения, возникающие в пара разделов.

Замена RAII в .NET - это шаблон использования, который работает почти так же, как только вы привыкнете к нему.

Ответ 4

Ближе всего вы дойдете до этого очень ограниченного оператора stackalloc.

Ответ 5

Есть несколько похожих потоков, если вы их ищете, но в основном, что это сводится к тому, что если вы хотите, чтобы RAII на .NET просто реализовал IDisposable-тип и использовал оператор "using" для получения детерминированного удаления. Таким образом, многие из тех же идеалов могут быть реализованы и использованы лишь немного более многословно.

Ответ 6

ИМХО, большие вещи, которые нужны VB.net и С#:

  1. "использование" объявления для полей, что заставит компилятор сгенерировать код для удаления всех отмеченных таким образом полей. Поведение по умолчанию должно быть для компилятора, чтобы сделать реализацию класса IDisposable, если он этого не сделал, или вставить логику удаления до начала основной процедуры утилизации для любого из нескольких общих шаблонов реализации IDisposal, или же использовать атрибут, чтобы указать, что вещи захоронения должны проходить в рутине с определенным именем.
  2. средство детерминированного удаления объектов, конструкторы и/или инициализаторы полей генерируют исключение либо по умолчанию (вызов метода удаления по умолчанию), либо пользовательское поведение (вызов метода с определенным именем).
  3. Для vb.net автоматически сгенерированный метод для удаления всех полей WithEvent.

    Все из них могут быть довольно хорошо удалены в vb.net, а в С# несколько хуже, но первоклассная поддержка для них улучшит оба языка.

Ответ 7

Вы можете сделать форму RAII в .net и java, используя методы finalize(). Перегрузка finalize() вызывается до того, как класс очищается GC и поэтому может быть использован для очистки любых ресурсов, которые не должны быть сохранены классом (мьютексы, сокеты, дескрипторы файлов и т.д.). Тем не менее он все еще не детерминирован.

С помощью .NET вы можете сделать некоторые из этого детерминистически с помощью интерфейса IDisposable и ключевого слова using, но это имеет ограничения (используя конструкцию, когда она используется для детерминированного поведения, по-прежнему не существует детерминированного освобождения памяти, не используется автоматически в классах и т.д.).

И да, я считаю, что идеи RAII должны быть внедрены в .NET и другие управляемые языки, хотя точный механизм можно обсуждать бесконечно. Единственной альтернативой, которую я мог бы увидеть, было бы введение GC, который мог бы обрабатывать произвольную очистку ресурсов (а не только память), но тогда у вас есть проблемы, когда указанные ресурсы должны быть обязательно деблокированы.