В прошлой теме, где рассматривалась реализация метода Dispose, говорилось, что для его вызова можно использовать следующую конструкцию try..catch:
Test(); void Test() { Person? tom = null; try { tom = new Person("Tom"); } finally { tom?.Dispose(); } } public class Person : IDisposable { public string Name { get;} public Person(string name) => Name = name; public void Dispose() => Console.WriteLine($"{Name} has been disposed"); }
Однако синтаксис C# также предлагает синонимичную конструкцию для автоматического вызова метод Dispose - конструкцию using:
using (Person tom = new Person("Tom")) { }
Конструкция using оформляет блок кода и создает объект некоторого типа, который реализует интерфейс IDisposable, в частности, его метод Dispose. При завершении блока кода у объекта вызывается метод Dispose.
Важно, что данная конструкция применяется только для типов, которые реализуют интерфейс IDisposable.
Ее использование:
Test(); void Test() { using (Person tom = new Person("Tom")) { // переменная tom доступна только в блоке using // некоторые действия с объектом Person Console.WriteLine($"Name: {tom.Name}"); } Console.WriteLine("Конец метода Test"); } public class Person : IDisposable { public string Name { get;} public Person(string name) => Name = name; public void Dispose() => Console.WriteLine($"{Name} has been disposed"); }
Консольный вывод:
Name: Tom Tom has been disposed Конец метода Test
Здесь мы видим, что по завершении блока using у объекта Person вызывается метод Dispose. Вне блока кода using объект tom не существует.
Начиная с версии C# 8.0 мы можем задать в качестве области действия всю окружающую область видимости, например, метод:
Test(); void Test() { using Person tom = new Person("Tom"); // переменная tom доступна только в блоке using // некоторые действия с объектом Person Console.WriteLine($"Name: {tom.Name}"); Console.WriteLine("Конец метода Test"); } public class Person : IDisposable { public string Name { get;} public Person(string name) => Name = name; public void Dispose() => Console.WriteLine($"{Name} has been disposed"); }
В данном случае using сообщает компилятору, что объявляемая переменная должна быть удалена в конце области видимости - то есть в конце метода Test. Соответственно мы получим следующий консольный вывод:
Name: Tom Конец метода Test Tom has been disposed
Для освобождения множества ресурсов мы можем применять вложенные конструкции using. Например:
void Test() { using (Person tom = new Person("Tom")) { using (Person bob = new Person("Bob")) { Console.WriteLine($"Person1: {tom.Name} Person2: {bob.Name}"); }// вызов метода Dispose для объекта bob } // вызов метода Dispose для объекта tom Console.WriteLine("Конец метода Test"); }
В данном случае обе конструкции using создают объекты одного и того же типа, но это могут быть и разные типы данных, главное, чтобы они реализовали интерфейс IDisposable.
Мы можем сократить это определение:
void Test() { using (Person tom = new Person("Tom")) using(Person bob = new Person("Bob")) { Console.WriteLine($"Person1: {tom.Name} Person2: {bob.Name}"); } // вызов метода Dispose для объектов bob и tom Console.WriteLine("Конец метода Test"); }
И, как уже было выше сказано, в C# мы можем задать в качестве области действия для объектов, создаваемых в конструкции using, весь метод:
private static void Test() { using Person tom = new Person { Name = "Tom" }; using Person bob = new Person { Name = "Bob" }; Console.WriteLine($"Person1: {tom.Name} Person2: {bob.Name}"); Console.WriteLine("Конец метода Test"); } // вызов метода Dispose для объектов bob и tom