Каково назначение оператора using в C#
Что проверяют
Знаете ли вы, что using гарантирует вызов Dispose(), и понимаете ли, что именно этот вызов освобождает. Следом почти всегда спрашивают, во что using разворачивает компилятор и чем он отличается от ручного try/finally.
Как отвечать
Сначала разведите два значения слова. Директива using в начале файла подключает пространства имён (плюс using static, псевдонимы и global using с C# 10). Оператор using управляет временем жизни объекта:
- блочная форма:
using (var stream = File.OpenRead(path)) { ... }. Компилятор превращает её вtry/finally, где вfinallyвызываетсяDispose(). Вызов произойдёт и при исключении, и приreturnиз середины блока; - using-объявление (C# 8):
using var conn = new SqlConnection(cs);без фигурных скобок.Dispose()вызывается в конце охватывающей области видимости, несколько объявлений освобождаются в обратном порядке; await using: для типов сIAsyncDisposable, вызываетDisposeAsync(). Нужен дляStream,DbConnection,DbContextв EF Core, когда код асинхронный;- требование к типу: реализовать
IDisposable, а дляref structдостаточно доступного методаDispose().
Главная мысль: Dispose() не освобождает управляемую память, это работа сборщика мусора. Он закрывает то, о чём GC не знает: файловые дескрипторы, сокеты, соединения с базой. У SqlConnection вызов Dispose() возвращает соединение в пул, а не рвёт его.
Где спотыкаются
Говорят, что using «предотвращает утечки памяти». Интервьюер сразу попросит объяснить разницу между управляемой памятью и неуправляемым ресурсом.
Оборачивают в using новый HttpClient на каждый запрос. Сокеты после закрытия какое-то время висят в TIME_WAIT, и под нагрузкой порты заканчиваются. Правильно держать один экземпляр или брать клиент из IHttpClientFactory.
Вызывают Dispose() у объекта, полученного из DI-контейнера. Временем жизни зарегистрированных сервисов управляет контейнер, и ручной using вокруг внедрённого DbContext ломает остальных потребителей в том же scope.