Прежде чем говорить непосредственно о конструкции using в С# и ее использовании, нужно вспомнить о таком процессе, как освобождение памяти для приложения. Во время создания любого объекта среда CLR
выделяет для него память из управляемой кучи. Естественно, память не безгранична и создание будет происходить до тех пор, пока есть свободное место. В С# и .NET существует сборщик мусора, который легко справляется с освобождением памяти в управляемых кучах.
Но что происходит, когда в программе необходимо задействовать неуправляемые объекты (открыть файлы, сетевые подключения, буферы вершин или индексов и так далее)? Сборщик мусора не умеет их удалять, а значит, разработчик должен самостоятельно убирать за собой.
Содержание статьи:
1. Удаление неуправляемых объектов в C#
2. Использование using с примером на структурах
3. Using: важные подробности
4. Итог по применению
Это означает, что для удаления неуправляемых объектов необходимо использовать либо деструктор, либо интерфейс System.IDisposable
. В первом случае, по сути, используется метод Finalize
из-за особенностей компиляции деструктора в C#. И тут нужно понимать, что когда неуправляемые объекты помещаются таким образом в кучу, сборщик мусора проверяет, имеет ли объект метод Finalize
.
После этого в отдельной таблице он сохраняет на него указатель, а удаляет уже при следующем прогоне. Это существенно сказывается на производительности. Чтобы удалить все необходимые объекты, в какой-то конкретный момент, используется интерфейс IDisposable
, а именно, метод Dispose
. С его помощью и происходит очистка от подобных объектов.
Поясним всю эту заумную теорию на каком-то живом примере: допустим, необходимо получить доступ к файлу, базе данных или любому другому ресурсу, который необходимо закрыть после использования.
Пример кода:
StreamReader sr = new StreamReader(@"d:\sometxt.txt"); sr.ReadToEnd().Count(); // производим любую операцию sr.Dispose(); // закрываем ресурс
Казалось бы, обычный процесс. Однако если файл, например, закодирован неправильно, или что-то происходит между открытием и закрытием ридера, то ресурс не будет закрыт до тех пор, пока сборщик мусора не избавится от него, что в некоторых случаях может означать, что память будет занята пока приложение не будет закрыто.
StreamReader sr = null; try { sr = new StreamReader(@"d:\sometxt.txt"); sr.ReadToEnd().Count(); } finally { if (sr != null) // если возникает ошибка StreamReader.ctr() sr.Dispose(); }
Основу этого кода составляет страхующая конструкция try...finally
, которая следит за тем, что удаление объектов из памяти в методе Dispose
произойдет даже если случится что-то непредвиденное.
Именно здесь мы наконец подошли к использованию конструкции using
в С#. Данный оператор принимает любой объект, реализующий IDisposable
. В конце операции или в случае какой-либо ошибки он безопасно избавляется от использованного объекта. По сути, делает то же самое, что и конструкция try...finally
, но экономит огромное количество строчек кода. Сравните с предыдущим примером:
using(StreamReader sr = new StreamReader(@"d:\sometxt.txt")) { sr.ReadToEnd().Count(); }
Если рассмотреть это на более простом примере, то аналогичный код, использующий try...finally
, длиною всего в 12 строк:
Person p=null; try { p = new Person(); } finally { if (p != null) { p.Dispose(); } }
При использовании конструкции using
в C# метод Dispose
вызывается автоматически, а вышеприведенный код превращается в трехстрочный:
using (Person p = new Person()) { }
Стоит также сразу подчеркнуть, что using
в C# используется только для тех классов, которые могут реализовывать интерфейс IDisposable
, иначе метод Dispose
не сработает.
Еще один пример использования using в C#:
using System; namespace Example { class Program { static void Main(string[] args) { Test(); Console.ReadLine(); } private static void Test() { using (Person p = new Person { Name = "Вова" }) { Console.WriteLine($"Выводим на экран имя объекта Person: {p.Name}"); } Console.WriteLine("Конец"); } } public class Person : IDisposable { public string Name { get; set; } public void Dispose() { Console.WriteLine("Disposed"); } } }
Консольный вывод:
Выводим на экран имя объекта Person: Вова Disposed Конец
Если же необходимо освобождать множество ресурсов, то можно использовать вложенные конструкции using
. Например:
private static void Test() { using (Person vova = new Person { Name = "Вова" }) using(Person dima = new Person { Name = "Дима" }) { Console.WriteLine($"Person1: {vova.Name} Person2: {dima.Name}"); } // вызов метода Dispose для объектов vova и dima Console.WriteLine("Конец"); }
Консольный вывод:
Person1: Вова Person2: Дима Конец
Когда необходимо производить очистку неуправляемых объектов, с которыми не справляется сборщик мусора, приходится реализовывать интерфейс IDisposable
, в частности, метод Dispose
(метод Finalize
не удаляет объекты сразу и плохо влияет на производительность). Для удаления объектов даже при возникновении каких-либо ошибок используется конструкция try...finally
. Именно этот подход является основным и самым верным способом избавления от неуправляемых объектов.
Вот пример сказанного:
class Example { static void Main() { char[] buffer = new char[75]; var streamReader = new StreamReader("test.txt"); try { int charsRead = 0; while (streamReader.Peek() != -1) { charsRead = streamReader.Read(buffer, 0, buffer.Length); } } finally { if (streamReader != null) { ((IDisposable)streamReader).Dispose(); } } } }
Оператор using
в C# заменяет эту конструкцию, что существенно упрощает написание кода для очистки объекта. Фактически, когда компилятор C# встречает using
, он производит ту же операцию, что и при использовании try...finally
, — реализует интерфейс IDisposable
, который объявляет, что происходит использование неуправляемого ресурса, от которого нужно избавиться методом Dispose
.
class Example { static void Main() { char[] buffer = new char[75]; using var streamReader = new StreamReader("test.txt"); int charsRead = 0; while (streamReader.Peek() != -1) { charsRead = streamReader.Read(buffer, 0, buffer.Length); } } }
Кроме того, с помощью using
в C# можно использовать несколько ресурсов в одном операторе. Говоря проще — можно применять вложенные инструкции using
. Главное, что конструкция using
в C# позволяет автоматически вызывать метод Dispose
даже при наличии ошибок, когда код выходит из блока.
А ведь обработка ошибок важна в основном для объектов IDisposable
, потому что именно для них последствия возникновения ошибки являются большой проблемой. Поэтому использование using
C# предоставляет разработчикам синтаксис базовой безопасности, чтобы добавить всего один блок и двигаться дальше, не переживая о загрузке памяти неуправляемыми объектами.
Для закрепления материала рекомендуем также посмотреть короткое видео на данную тему:
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…