Зачем нужны конструкции using в C#
Что проверяют
Различаете ли вы три значения ключевого слова и понимаете ли, что Dispose не освобождает управляемую память. Следом часто спрашивают, во что using компилируется и почему HttpClient в using на каждый запрос это ошибка.
Как отвечать
Назовите все формы, а не одну:
- директива
using: подключает пространства имён. Сюда жеusing staticдля статических членов, псевдонимы вродеusing Json = System.Text.Json;иglobal usingиз C# 10, который действует на весь проект; - оператор
using (...) { }: гарантирует вызовDispose()у объекта, реализующегоIDisposable, при выходе из блока, в том числе по исключению; - объявление
using var x = ...;(C# 8): тот же вызов, но в конце области видимости, без лишнего уровня вложенности; await using(C# 8): дляIAsyncDisposable, вызываетDisposeAsync(). Нужен дляDbContext, асинхронных потоков и соединений.
Под капотом компилятор разворачивает оператор в try/finally с проверкой на null и вызовом Dispose в finally. Отсюда и смысл конструкции: детерминированно закрыть то, что не является просто памятью. Это дескрипторы файлов, сокеты, соединения с базой, блокировки. Сам объект после Dispose не уничтожается, память под него соберёт GC, когда до него дойдёт. Для типов с неуправляемыми ресурсами есть ещё финализатор или SafeHandle: страховка на случай, если Dispose забыли вызвать.
Где спотыкаются
Говорят, что using предотвращает утечки памяти и удаляет объект. Он вызывает Dispose, а управляемую память освобождает только сборщик мусора.
Создают HttpClient в using на каждый запрос. Сокеты остаются в TIME_WAIT, под нагрузкой заканчиваются порты. Правильно держать долгоживущий экземпляр или брать клиента из IHttpClientFactory.
Не замечают, что using var держит ресурс до конца метода. Соединение с базой или файл остаются открытыми, пока идёт долгая обработка после работы с ними, и в таком месте лучше блочная форма.