Что значит очистка неуправляемых ресурсов в C#
Что проверяют
Понимаете ли границу между тем, что освобождает сборщик мусора, и тем, чего он не видит вовсе. И знаете ли принятый способ освобождения: IDisposable, using, SafeHandle, а финализатор только как страховка.
Как отвечать
Сборщик мусора отвечает только за управляемую кучу. Всё, что выдала операционная система или нативная библиотека, он не отслеживает: дескрипторы файлов и сокетов, хендлы окон и устройств, память из Marshal.AllocHGlobal, объекты COM. Это и есть неуправляемые ресурсы, освобождать их надо явно.
Схема ответа:
- класс, владеющий таким ресурсом, реализует
IDisposable, а потребитель вызываетDisposeчерезusing; - в современном C# пишут объявление вида
using var stream = File.OpenRead(path);, освобождение произойдёт в конце области видимости; - для асинхронного освобождения есть
IAsyncDisposableиawait using; - страховка на случай забытого
Disposeэто финализатор, но он дорогой, откладывает сборку объекта на лишний цикл и порядок вызова не гарантирует; поэтому нативный хендл заворачивают в наследникаSafeHandle, где финализатор уже написан правильно; - в
DisposeзовутGC.SuppressFinalize(this), чтобы объект с финализатором не попал в очередь финализации зря.
Классический шаблон: публичный Dispose() плюс защищённый Dispose(bool disposing), где при значении true освобождают вложенные управляемые объекты, а нативное освобождают в обоих случаях.
Где спотыкаются
Называют неуправляемым ресурсом FileStream или подключение к базе. Это управляемые обёртки, внутри у них уже есть безопасный хендл; для таких полей достаточно вызвать Dispose, свой финализатор писать не нужно и вредно.
Второе: ждут от небрежности утечку памяти. Чаще заканчиваются не байты, а дескрипторы и соединения в пуле, и падает это далеко от места ошибки.
Третье: считают финализатор основным путём. При завершении процесса финализаторы могут не выполниться вовсе, а до тех пор ресурс держится всё время жизни объекта.