Это перевод первой части статьи. Статья была написана в 2008 году. Спустя 10 лет почти не потеряла актуальности. В течение более чем 20-летнего опыта кодирования я иногда разрабатывал собственные языки для решения задач. Они варьировались от простых имеративных языков до специализрованных регулярных выражений для деревьев. При создании языков есть множество рекомендаций и некоторые простые правила не должны нарушаться. Одно из них:  Угадайте какой рекомендации не следует рантайм .NET, и как следствие все языки на его базе? Причина по которой существует данное правило — детерминированное освобождение ресурсов необходимо для создания поддерживаемых программ. Детерминированное освобождение ресурсов обеспечивает определенную точку, в которой программист уверен, что ресурс освобожден. Существует два способа написания надежных программ: традиционный подход — освобождать ресурсы как можно раньше и соверменный подход — с освобождением ресурсов в течение неопределенного времени. Преимущество современного подхода в том, что программисту не надо явно освобождать ресурсы. Недостаток в том, что гораздо сложнее написать надежное приложение, появляется много трудноуловимых ошибок. К сожалению рантайм .NET создан с использованием современного подхода. .NET поддерживает недетерминированное освобождение ресурсов с помощью метода Finalize, который имеет специальное значение. Для детерминированного освобождения ресурсов Microsoft также добавил интерфейс IDisposable (и другие классы, которые мы рассмотрим позже). Тем не менее для рантайма IDisposable это обычный интерфейс, как и все остальные. Такой статус "второсортного" создает некоторые сложности. В C# "детерминированное освобождение для бедных" может быть реализовано с помощью операторов try и finally или using (что почти тоже самое). В Microsoft долго обсуждали делать ли счетчики ссылок или нет, и мне кажется, что было принято неверное решение. В результате для детерминированного освобождение ресурсов нужно использовать неуклюжие конструкции finally\using или прямой вызов IDisposable.Dispose, что чревато ошибками. Для С++ программиста, который привык использовать shared_ptr<T> оба варианта не привлекательны. (последнее предложение дает понять откуда у автора такое отношение — прим. пер.) IDisposable — решение для детерминированного освобождения ресурсов, предлагаемое Miсrosoft. Одно предназначено для следующих случаев:  IDisposable помогает детерминированно освобождать ресурсы, но имеет свои проблемы. Объекты IDisposable использоватькорректно довольно громоздко. Использование объекта нужно обернуть в конструкцию using. Плохо то, что C# не позволяет использовать using с типом, не реализующим IDisposable. Поэтому программист должен каждый раз обращаться к документации чтобы понять надо ли писать using, или просто писать using везде, а потом стирать там, где ругается компилятор. Managed C++ в этом отношении гораздо лучше. Он поддерживает стековую семантику для ссылочных типов, которая работает как using только для тех типов, где это необходимо. C# мог бы выиграть от возможности писать using с любым типом. Эта проблема может быть решена с помощью. инструментов анализа кода. Ухудшает ситуцию то, что если забыть using, то программа может пройти тесты, но упасть во время работы "в полях". IDisposable вместо подсчета ссылок несет другую проблему — определение владельца. Когда в C++ последня копия shared_ptr<T> выходит из области видимости ресурсы освобождаются сразу же, не надо думать кто должен освобождать. IDisposable напротив заставляет программиста определять кто "владеет" объектом и ответственнен за его освобождение. Иногда владение очевидно: когда один объект инкапсулирует другой и сам реализует IDisposable, следовательно отвечает за освобождение дочерних объектов. Бывает время жизни объекта определяется блоком кода и программист просто использует using вокруг этого блока. Тем не менее существует много случаев, где объект может быть использован в нескольких местах и его время жизни определить сложно (хотя в этом случае подсчет ссылок справился бы прекрасно). Добавление IDisposable к классу и убирание IDisposable из списка реализуемых интерфейсов — это ломающее изменение. Клиентский код, который не ожидает IDisposable, то не освободит ресурсы, если вы добавите IDisposable к одному из своих классов, передаваемых по ссылке на интерфейс или базовый класс. Microsoft сам столкнулся с этой проблемой. IEnumerator не наследуется от IDisposable, а IEnumerator<T> наследуется. Если коду, принимающему IEnumerator передать IEnumerator<T>, то Dispose не будет вызван. Это не конец света, но выдает некоторую второстепенную сущность IDisposable. Самый большой недостаток вызванный IDisposable в области проектирования иерархии — каждый класс и интерфейс должен предсказать понадобятся ли его наследникам IDisposable. Если интерфейс не наследует IDisposable, но классы реализующие интерфейс реализуют также IDisposable, то конечный код будет или игнорировать детерминированное освобождение, или сам должен проверять реализует ли объект интерфейс IDisposable. Но для этого уже не получится использовать конструкцию using и придется писать уродский try и finally. Короче говоря IDisposable усложняет разработку повторно используемого софта. Ключевая прчина это нарушение одного из приципов объектно-ориентированного проектирования — разделения интерфейса и реализации. Освобождение ресурсов должно быть деталью реализации. Microsoft решил сделать детерминированное освобождение ресурсов интерфейсом второго сорта. Одно из не очень красивых решений — сделать все классы реализующими IDisposable, но в подавляющем большинстве классов IDisposable.Dispose не будет делать ничего. Но это слишком не красиво. Еще одна сложность IDisposable — коллекции. Часть коллекций "владеют" объектами в них, а часть нет. При этом коллекции сами не реализуют IDisposable. Программист должен не забывать вызывать IDisposable.Dispose для объектов в коллекции или создавать своих наследников классов коллекций, которые реализуют IDisposable чтобы обозначить "владение". IDisposable может быть вызван явно в любое время, независимо от времени жизни объекта. То есть к каждому объекту добавляется состояние "освобожден", в котором рекомендуется выбрасывать исключение ObjectDisposedException. Проверка состояния и выбрасывание исключений — дополнительные расходы. Вместо проверок на каждый чих, лучше считать обращение к объекту в "освобожденном" состоянии "неопределенным поведением", как обращение к освобожденной памяти. IDisposable это всего лишь интерфейс. Класс, реализующий IDisposable, поддерживает детерминированное освобождение, но не гарантирует его. Для клиентского кода вполне нормально не вызывать Dispose. Поэтому класс, реализующий IDisposable, должен поддерживать как детерминированное, так и недетерминированное освобождение. Microsoft предлагает патерн для реализации IDisposable. (Ранее был вообще ужасный паттерн, но относительно недавно, после появления .NET 4, документацию поправили, в том числе под влиянием этой статьи. В старых редакциях книг по .NET вы можете найти старый вариант. — прим. пер. ) Кроме того: Завершение работы объекта — часто возникает в программах параллельными или асинхронными потоками. Например класс использует отдельный поток и хочет завершить его с помощью ManualResetEvent. Это вполне можно сделать в IDisposable.Dispose, но может приводить к ошибке если код вызывать в финализаторе.  Чтобы понять ограничения в финализаторе надо понимать как работает сборщик мусора. Ниже упрощенная схема, в которой опущены многие детали, связанные с поколениями,, слабыми ссылками, возрождением объектов, фоновой сборкой мусора итд. Сборщик мусора .NET использует алгоритм mark-and-sweep. В целом логика выглядит так: В фоне работает поток (или несколько) финализации: Теперь должно быть понятно почему нельзя обращаться из финализатора к управляемым ресурсам — вы не знаете в каком порядке вызываются финализаторы. Даже вызов IDisposable.Dispose другого объекта из финализатора может привести к ошибке, так как код освобождения ресурсов может работать в другом потоке. Есть несколько исключений, когда можно обращаться к управляемым ресурсам из финализатора: В общем случае лучше не обращаться к управлемым ресурсам из финализаторов. Тем не менее логика завершения необходима нетривиального софта. В Windows.Forms содержит логику завершения в методе Application.Exit. Когда вы разрабатываете свою библиотеку компонентов лучше всего логику завершения завязать на IDisposable. Нормальное завершение в случае вызова IDisposable.Dispose и экстренное в противном случае. Microsoft тоже столкнулась с этой проблемой. Класс StreamWriter владеет объектом Stream (в зависимости от параметров конструктора в последней версии — прим. пер.). StreamWriter.Close сбрасывает буфер и вызывает Stream.Close (тоже просиходит если обернуть в using — прим. пер.). Если StreamWriter не закрыт, буфер не сбрасывается и чатсь данных теряется. Microsoft просто не переопределил финализатор, таким образом "решив" проблему завершения. Прекрасный пример нужности логики завершения. Много информации о внутреннем устройстве .NET в этой статье почерпнуто из книги "CLR via C#" джеффри Рихтера. Если у вас её еще нет, то купите. Серьезно. Это необходимые знания для любого C# программиста. Большинство .NET программистов никогда не столкнется с проблемами, описанными в этой статье. .NET развиватся в сторону повышения уровня абстракции и уменьшении потребности в "жонглировании" неуправляемыми ресусрами. Тем не менее эта статья полезна тем, что описывает глубокие детали простых вещей и их влияние на проектирование кода. В следующей части будет подробный разбор как правильно работать с управляемыми и неуправляемыми ресурсами в .NET с кучей примеров.