using и IDisposable
Правило «уходя, выключите плиту» можно не держать в голове, а встроить в саму плиту. У некоторых плит есть автоматическое отключение: вы уходите из кухни, и плита выключается сама, как бы вы ни ушли. В C# такую автоматику даёт инструкция using.
Зачем она нужна? Некоторые объекты держат то, что надо освободить, когда работа закончена: открытый файл, сетевое соединение. Если этого не сделать, файл останется занят, а соединение — открыто.
Интерфейс IDisposable
Если объект держит файл или соединение, его класс реализует интерфейс IDisposable. В нём один метод — Dispose(): он освобождает то, что держит объект.
Вызвать Dispose() можно и вручную. Но если между созданием объекта и этим вызовом случится исключение, до вызова дело не дойдёт. Вызов в блоке finally выполнится и при исключении, а using — короткая форма такого блока.
Инструкцию using не путайте с директивой using из темы о строках: директива подключает к файлу пространство имён, а инструкция вызывает Dispose() в конце блока.
Две формы using
Инструкция using
Объект создают в скобках после using, а работу с ним пишут в фигурных скобках:
using (var stove = new Stove()) // Stove on
{
Console.WriteLine("Cooking"); // Cooking
} // Stove off
Console.WriteLine("Done"); // Done
class Stove : IDisposable
{
public Stove() => Console.WriteLine("Stove on");
public void Dispose() => Console.WriteLine("Stove off");
}Давайте разберём программу по шагам:
new Stove()создаёт плиту, и конструктор печатаетStove on.using (var stove = …)запоминает объект, чтобы в конце блока вызвать у негоDispose().- Тело блока печатает
Cooking. - Когда выполнение доходит до закрывающей скобки,
usingвызываетstove.Dispose(), и печатаетсяStove off. - Затем выполняется строка после блока и печатает
Done.
Объявление using var
Короткая форма обходится без скобок и своего блока: using var stove = new Stove();. Dispose() вызывается в конце того блока, где стоит объявление. Это происходит, даже когда исключение покидает блок:
try
{
using var stove = new Stove(); // Stove on
Console.WriteLine(int.Parse("x"));
} // Stove off
catch (FormatException)
{
Console.WriteLine("Not a number"); // Not a number
}Этот фрагмент продолжает первую программу: поставьте его перед классом Stove. Разберём и этот пример:
- Объявление создаёт плиту, и печатается
Stove on. int.Parse("x")выбрасывает исключение, и оно покидает блокtry.- Когда исключение покидает блок,
usingвызываетDispose(), и печатаетсяStove off. - Только после этого
catchпечатаетNot a number.
Аналогия с автоматикой неточна в одном. Dispose() вызывается не тогда, когда объект перестал быть нужен, а строго в конце блока. Пока блок не кончился, плита включена, даже если вы ею уже не пользуетесь.
using — это try и finally
Этот вопрос часто задают на собеседовании: во что компилятор превращает using? Почти в такой код:
var stove = new Stove(); // Stove on
try
{
Console.WriteLine("Cooking"); // Cooking
}
finally
{
stove.Dispose(); // Stove off
}
class Stove : IDisposable
{
public Stove() => Console.WriteLine("Stove on");
public void Dispose() => Console.WriteLine("Stove off");
}Объект создаётся до try, тело блока попадает в try, а вызов Dispose() — в finally. Поэтому using вызывает Dispose() на любом выходе из блока: в конце, после return и после исключения.
Частая ошибка
В using положили объект класса, который не реализует IDisposable:
using (var box = new Box()) // ✗
{
Console.WriteLine("Inside");
}
class Box
{
public Box() => Console.WriteLine("Box ready");
}error CS1674: 'Box': type used in a using statement must implement 'System.IDisposable'.Программа не компилируется. using принимает только типы, которые реализуют IDisposable, а Box его не реализует. Свой метод Dispose() тоже не помог бы: компилятор ищет именно интерфейс. using нужен только объектам, которым есть что освобождать, а обычный объект создают без него: var box = new Box();.
Главное
- Класс, объект которого держит файл или соединение, реализует
IDisposableи его методDispose(). using (var x = …) { }иusing var x = …;вызываютDispose()в конце блока, даже если блок покинуло исключение.- Компилятор превращает
usingвtryиfinallyс вызовомDispose()вfinally. usingс объектом класса безIDisposableне компилируется: ошибкаCS1674.