Рубріки: Теория

Конструкция using в C#: минимум, который нужно знать каждому

Юрий Кузнецов

Прежде чем говорить непосредственно о конструкции using в С# и ее использовании, нужно вспомнить о таком процессе, как освобождение памяти для приложения. Во время создания любого объекта среда CLR выделяет для него память из управляемой кучи. Естественно, память не безгранична и создание будет происходить до тех пор, пока есть свободное место. В С# и .NET существует сборщик мусора, который легко справляется с освобождением памяти в управляемых кучах.

Но что происходит, когда в программе необходимо задействовать неуправляемые объекты (открыть файлы, сетевые подключения, буферы вершин или индексов и так далее)? Сборщик мусора не умеет их удалять, а значит, разработчик должен самостоятельно убирать за собой.

Содержание статьи:
1. Удаление неуправляемых объектов в C#
2. Использование using с примером на структурах
3. Using: важные подробности
4. Итог по применению

Удаление неуправляемых объектов в C#

Это означает, что для удаления неуправляемых объектов необходимо использовать либо деструктор, либо интерфейс 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 с примером на структурах

Именно здесь мы наконец подошли к использованию конструкции 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: важные подробности

Стоит также сразу подчеркнуть, что 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), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…

21.11.2024

Что такое PWA приложение? Зачем необходимо прогрессивное веб-приложение

Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…

19.11.2024

Как создать игру на телефоне: программирование с помощью конструктора

Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…

17.11.2024

Google Bard: эффективный аналог ChatGPT

В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…

14.11.2024

Скрипт и программирование: что это такое простыми словами

Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…

12.11.2024

Дедлайн в разработке: что это такое простыми словами

Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…

11.11.2024