Основы разработки на языке C#

Этот курс охватывает ключевые концепции языка C# и платформы .NET, от базового синтаксиса до продвинутых техник. Вы изучите объектно-ориентированное программирование, работу с данными и асинхронность для создания современных приложений.

1. Введение в экосистему .NET, типы данных и управляющие конструкции

Введение в экосистему .NET, типы данных и управляющие конструкции

Добро пожаловать на курс «Основы разработки на языке C#». Это первая статья, с которой начнется ваше погружение в мир профессиональной разработки программного обеспечения. C# (произносится как «си-шарп») — это мощный, строго типизированный объектно-ориентированный язык программирования, разработанный компанией Microsoft. Сегодня он используется повсюду: от создания мобильных приложений и игр до разработки сложных корпоративных систем и искусственного интеллекта.

В этой статье мы разберем фундамент, на котором строится весь язык: платформу .NET, базовые типы данных и конструкции, позволяющие управлять ходом выполнения программы.

Что такое экосистема .NET?

Многие новички путают понятия C# и .NET. Давайте внесем ясность. C# — это язык, то есть набор правил и синтаксиса, с помощью которых мы пишем код. .NET — это платформа (или экосистема), которая позволяет этому коду работать.

Представьте, что C# — это автомобиль, а .NET — это двигатель, топливная система, колеса и даже дорога, по которой этот автомобиль едет. Без платформы язык программирования остался бы просто текстом в редакторе.

Как работает .NET?

Когда вы пишете код на C#, компьютер не понимает его напрямую. Процесс запуска программы выглядит следующим образом:

  • Компиляция: Ваш код на C# преобразуется компилятором в промежуточный язык, называемый IL (Intermediate Language).
  • Выполнение: При запуске программы в дело вступает CLR (Common Language Runtime) — общеязыковая среда выполнения. Она берет IL-код и «на лету» переводит его в машинный код, понятный конкретному процессору.
  • !Схема превращения исходного кода C# в машинные инструкции через CLR

    Благодаря такой архитектуре, программы на .NET могут работать на разных операционных системах (Windows, Linux, macOS), если там установлена соответствующая версия среды выполнения.

    Структура простейшей программы

    Давайте посмотрим, как выглядит минимальная программа на C#. Традиционно мы начнем с вывода фразы «Hello, World!».

    Разберем этот код по строкам:

    * using System; — подключение пространства имен System. Это как библиотека, в которой хранятся базовые инструменты, например, работа с консолью. * namespace HelloWorldApp — пространство имен вашего приложения. Это контейнер, помогающий организовать код и избежать конфликтов имен. * class Program — объявление класса. В C# весь код должен находиться внутри классов. * static void Main — это точка входа. Именно с этого метода начинается выполнение любой консольной программы.

    Переменные и типы данных

    C# — это язык со строгой статической типизацией. Это означает, что каждая переменная должна иметь определенный тип, и этот тип не может внезапно измениться в процессе работы программы (за редкими исключениями). Это помогает избегать множества ошибок еще на этапе написания кода.

    Объявление переменных

    Чтобы создать переменную, нужно указать её тип и имя:

    Основные типы данных

    В C# существует множество типов данных, но для начала нам понадобятся самые основные. Их можно разделить на простые (значимые) и ссылочные.

    | Тип C# | Описание | Пример значения | | :--- | :--- | :--- | | int | Целое число | 42, -10, 0 | | double | Дробное число (с плавающей точкой) | 3.14, -0.01 | | bool | Логическое значение | true, false | | char | Одиночный символ | 'A', '7', '!' | | string | Строка (текст) | "Привет, мир!" |

    Диапазоны значений

    Важно понимать, что числовые типы имеют ограничения по памяти. Например, тип int занимает в памяти 32 бита. Давайте рассчитаем, какое максимальное число можно в него записать. Так как один бит тратится на знак (плюс или минус), для самого числа остается 31 бит.

    Формула для вычисления максимального значения знакового целого типа выглядит так:

    Где — максимальное значение, которое может хранить переменная, а — количество бит, выделенных под этот тип данных (для int это 32).

    Соответственно, минимальное значение:

    Где — минимальное возможное значение, а — разрядность типа (количество бит).

    Если подставить 32 вместо , мы получим диапазон от -2,147,483,648 до 2,147,483,647. Если вы попытаетесь записать число больше этого диапазона, произойдет ошибка переполнения.

    Управляющие конструкции

    Код, который выполняется просто сверху вниз, редко бывает полезен. Нам часто нужно принимать решения или повторять действия. Для этого используются управляющие конструкции.

    Условные операторы (if / else)

    Конструкция if позволяет выполнить блок кода, только если определенное условие истинно (true).

    Здесь программа проверяет условия по очереди. Как только одно из них сработает, остальные будут проигнорированы.

    Конструкция switch

    Если вам нужно проверить одну переменную на множество конкретных значений, удобнее использовать switch:

    Обратите внимание на ключевое слово break — оно необходимо, чтобы выйти из конструкции switch после выполнения нужного блока.

    Циклы

    Циклы позволяют выполнять один и тот же код многократно.

    #### Цикл while

    Выполняется до тех пор, пока условие истинно. Проверка происходит перед каждой итерацией.

    #### Цикл do-while

    Похож на while, но гарантирует, что код выполнится хотя бы один раз, так как проверка условия происходит после итерации.

    #### Цикл for

    Самый популярный цикл, когда мы заранее знаем, сколько раз нужно повторить действие. Он состоит из инициализации, условия и итератора.

    > Совет: Используйте цикл for, когда работаете со счетчиками, и while, когда количество повторений заранее неизвестно и зависит от внешних условий.

    Заключение

    Сегодня мы сделали первый шаг в изучении C#. Мы узнали, что C# работает поверх мощной платформы .NET, которая берет на себя управление памятью и выполнение кода. Мы рассмотрели базовые «кирпичики» данных: целые числа (int), строки (string), логические значения (bool). Также мы научились управлять потоком программы с помощью условий и циклов.

    В следующих статьях мы углубимся в объектно-ориентированное программирование, узнаем, что такое классы и объекты, и как создавать сложные структуры данных.

    2. Принципы ООП: инкапсуляция, наследование, полиморфизм и абстракция

    Принципы ООП: инкапсуляция, наследование, полиморфизм и абстракция

    В предыдущей статье мы познакомились с синтаксисом C#, типами данных и управляющими конструкциями. Мы писали код внутри метода Main, и он выполнялся последовательно. Такой подход называется процедурным программированием. Он отлично подходит для простых скриптов, но когда речь заходит о крупных системах — играх, банковских приложениях или веб-сервисах — процедурный код становится трудно поддерживать.

    Здесь на сцену выходит Объектно-Ориентированное Программирование (ООП). Это парадигма, которая предлагает взглянуть на программу не как на набор инструкций, а как на взаимодействие объектов, подобных объектам реального мира.

    C# — это язык, спроектированный вокруг ООП. Чтобы писать на нем профессионально, недостаточно знать синтаксис if и for. Нужно понимать четыре кита, на которых держится эта методология: инкапсуляция, наследование, полиморфизм и абстракция.

    !Четыре основных принципа ООП, составляющие фундамент разработки на C#

    Классы и объекты: краткое напоминание

    Прежде чем переходить к принципам, закрепим базу. В C# все строится вокруг классов и объектов.

    * Класс — это чертеж, шаблон или схема. Он описывает, какими свойствами и поведением будет обладать сущность. * Объект (или экземпляр) — это конкретная реализация этого чертежа, созданная в памяти компьютера.

    Представьте, что у архитектора есть чертеж дома (Класс). По этому чертежу можно построить один дом, целую улицу или даже город (Объекты). Дома могут отличаться цветом стен или жильцами (данными), но структура у них общая.

    Теперь разберем четыре главных принципа, которые превращают набор классов в мощную архитектуру.

    1. Инкапсуляция

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

    Суть инкапсуляции можно выразить фразой: «Скрывай внутреннюю реализацию и предоставляй доступ только к тому, что необходимо».

    Представьте капсулу с лекарством. Вы не знаете точный химический состав порошка внутри, и вам не нужно его трогать руками. Вы просто проглатываете капсулу (используете публичный интерфейс), и она работает.

    Модификаторы доступа

    В C# инкапсуляция реализуется с помощью модификаторов доступа:

    * public: доступно всем. * private: доступно только внутри этого класса. * protected: доступно внутри класса и его наследников.

    Проблема открытых полей

    Если мы сделаем все поля public, любой программист сможет записать туда некорректные данные.

    Решение через свойства

    Правильный подход — скрыть поле (private) и дать доступ через свойства (Properties).

    Таким образом, объект сам контролирует свое состояние. Это и есть инкапсуляция: данные защищены, а взаимодействие идет через методы.

    2. Наследование

    Наследование — это механизм, позволяющий создавать новый класс на основе уже существующего. Новый класс (наследник) перенимает все свойства и методы родительского класса и может добавлять свои собственные.

    Этот принцип позволяет соблюдать правило DRY (Don't Repeat Yourself) — не повторяйся. Если у вас есть код, общий для нескольких сущностей, вынесите его в базовый класс.

    Пример из жизни

    У нас есть Человек. У него есть имя и возраст. Есть Студент и Преподаватель. И студент, и преподаватель являются людьми. Вместо того чтобы писать поля Name и Age в каждом классе, мы используем наследование.

    Магия полиморфизма

    Самое интересное происходит, когда мы работаем с группой разных объектов как с базовым типом:

    Программа сама поймет, какой именно метод CalculateArea запустить — для круга или для квадрата. Это позволяет писать гибкий код, который легко расширять новыми типами фигур, не ломая существующую логику.

    !Один интерфейс вызова приводит к разному поведению у разных объектов

    4. Абстракция

    Абстракция — это способ выделить главные характеристики объекта, отбросив второстепенные детали. Это высший уровень проектирования.

    В примере с фигурами выше, класс Shape имел метод CalculateArea, который выводил текст "Площадь неопределенной фигуры". Но имеет ли смысл создавать объект просто «Фигура»? В реальности не бывает абстрактных фигур, бывают конкретные круги или квадраты.

    Чтобы запретить создание экземпляров базового класса и обязать наследников реализовать методы, используются абстрактные классы.

    Абстрактные классы

    Абстракция позволяет нам проектировать систему на уровне контрактов: «Я знаю, что это животное, и оно точно умеет издавать звук, но мне неважно, как именно оно это делает».

    Интерфейсы

    Еще более чистый вид абстракции в C# — это интерфейсы (interface). Интерфейс говорит что объект умеет делать, но не говорит как.

    Если абстрактный класс — это «недостроенный дом», то интерфейс — это список требований к строителю.

    Класс может наследовать только от одного класса (родителя), но может реализовывать сколько угодно интерфейсов. Это дает огромную гибкость.

    Взаимосвязь принципов

    Эти четыре принципа редко работают поодиночке. В хорошем приложении они переплетены:

  • Абстракция помогает спроектировать общую структуру системы (интерфейсы и базовые классы).
  • Наследование позволяет избежать дублирования кода при реализации этой структуры.
  • Инкапсуляция защищает данные внутри каждого конкретного класса.
  • Полиморфизм позволяет системе работать с разными объектами унифицированным способом.
  • Заключение

    Понимание ООП — это переломный момент в обучении разработчика. Сначала это может показаться сложным: зачем создавать столько классов, интерфейсов и свойств, если можно просто написать код в Main? Но по мере роста ваших программ вы увидите, что именно ООП спасает проект от хаоса, делая его управляемым, расширяемым и надежным.

    В следующих статьях мы углубимся в детали работы с коллекциями и узнаем, как эффективно хранить и обрабатывать группы объектов.

    3. Коллекции, массивы и использование обобщений (Generics)

    Коллекции, массивы и использование обобщений (Generics)

    В предыдущих статьях мы научились создавать классы и объекты, а также разобрали принципы объектно-ориентированного программирования. Мы создавали одиночные объекты: одного студента, одну машину или один банковский счет. Но реальные программы редко работают с единичными данными. Чаще всего нам нужно хранить списки пользователей, каталоги товаров, историю транзакций или очередь сообщений.

    Сегодня мы разберем, как эффективно управлять группами объектов в C#. Мы пройдем путь от простых массивов до мощных обобщенных коллекций, которые являются стандартом в современной разработке .NET.

    Массивы: фундамент хранения данных

    Массив (Array) — это самая базовая структура данных, представляющая собой набор элементов одного типа, расположенных в памяти последовательно друг за другом.

    Представьте массив как ряд пронумерованных почтовых ящиков. Если вы знаете номер ящика (индекс), вы можете мгновенно получить его содержимое.

    Объявление и инициализация

    В C# массивы имеют фиксированный размер. Это значит, что при создании массива вы должны сказать компьютеру: «Выдели мне место ровно под 5 чисел». Изменить этот размер позже нельзя.

    Доступ к элементам и память

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

    Формула вычисления адреса элемента массива:

    Где — адрес искомого элемента в памяти, — адрес начала массива (нулевого элемента), — индекс элемента, к которому мы обращаемся, а — размер одного элемента в байтах (например, для int это 4 байта).

    Благодаря этой формуле доступ к элементу с индексом 0 и индексом 1,000,000 происходит за одинаковое время. Это называется доступом за константное время — .

    Недостатки массивов

    Несмотря на скорость, массивы неудобны для многих задач:

  • Фиксированный размер. Если вы создали массив на 10 пользователей, а зарегистрировался 11-й, вам придется создавать новый массив большего размера и копировать туда все данные вручную.
  • Сложность вставки и удаления. Чтобы удалить элемент из середины массива, нужно сдвинуть все последующие элементы на одну позицию влево, чтобы закрыть «дыру».
  • Для решения этих проблем в C# существуют коллекции.

    Проблема типизации и появление Generics

    В ранних версиях C# (до 2.0) коллекции работали с типом object. Это позволяло класть в один список и числа, и строки, и объекты классов. Но это порождало две проблемы:

  • Безопасность типов: Вы могли случайно положить строку в список чисел, и программа падала с ошибкой только во время выполнения.
  • Производительность: Упаковка (boxing) простых типов (как int) в объекты и распаковка (unboxing) обратно требовали много ресурсов процессора.
  • Решением стали Обобщения (Generics). Обобщения позволяют вам написать код, который работает с данными любого типа, но этот тип указывается строго в момент создания объекта.

    Синтаксис обобщений использует угловые скобки <T>, где T — это тип данных, с которым будет работать коллекция.

    Список: List<T>

    Самая популярная коллекция в C# — это List<T> (Список). Она находится в пространстве имен System.Collections.Generic.

    List<T> — это «умный массив». Внутри он использует обычный массив, но умеет автоматически расширяться, когда место заканчивается.

    !Сравнение фиксированного массива и динамического списка List<T>

    Пример использования списка

    Поиск по ключу в словаре работает невероятно быстро, почти так же быстро, как доступ по индексу в массиве, благодаря механизму хеширования.

    Очереди и Стеки

    Существуют специализированные коллекции, которые диктуют порядок обработки элементов.

    Queue<T> (Очередь)

    Работает по принципу FIFO (First In, First Out — «Первым пришел, первым ушел»). Это как живая очередь в магазине.

    Stack<T> (Стек)

    Работает по принципу LIFO (Last In, First Out — «Последним пришел, первым ушел»). Представьте стопку тарелок: вы кладете тарелку сверху и берете тоже сверху. Ту, что положили первой (в самый низ), вы возьмете последней.

    Итерация по коллекциям: foreach

    Для перебора элементов любой коллекции (массива, списка, словаря) в C# используется удобный цикл foreach. Он автоматически проходит от первого до последнего элемента.

    Вам не нужно создавать счетчик i или следить за выходом за границы массива. foreach делает код чище и безопаснее.

    Когда и что использовать?

    Чтобы закрепить материал, давайте составим небольшую памятку по выбору структуры данных:

    | Ситуация | Рекомендуемая коллекция | | :--- | :--- | | Количество элементов известно заранее и не меняется | Массив (T[]) | | Нужно часто добавлять/удалять элементы, количество неизвестно | List<T> | | Нужно искать элементы по уникальному идентификатору (имя, ID, артикул) | Dictionary<TKey, TValue> | | Нужно обрабатывать данные в порядке поступления | Queue<T> | | Нужно обрабатывать данные в обратном порядке (как кнопку «Назад» в браузере) | Stack<T> |

    Заключение

    Коллекции и обобщения (Generics) — это инструменты, без которых невозможно написать современное приложение на C#. Они обеспечивают безопасность типов, высокую производительность и удобство работы с данными.

    Мы узнали, что массивы хороши для статичных данных, списки — для динамических, а словари — для быстрого поиска. В следующей части курса мы рассмотрим, как обрабатывать эти коллекции еще элегантнее с помощью технологии LINQ.

    4. Делегаты, события и работа с данными через LINQ

    Делегаты, события и работа с данными через LINQ

    В предыдущей статье мы разобрали, как хранить группы объектов, используя массивы и коллекции (List, Dictionary). Теперь перед нами встает следующая задача: как заставить эти объекты взаимодействовать друг с другом и как эффективно обрабатывать данные, которые в них хранятся?

    Представьте, что вы пишете игру. Когда игрок нажимает кнопку «Огонь», должно произойти множество вещей: проиграться звук выстрела, уменьшиться количество патронов, обновиться интерфейс. Как связать нажатие кнопки с этими действиями, не превращая код в запутанный клубок?

    В этой статье мы изучим три мощных инструмента C#, которые решают эти задачи: делегаты (передача методов как параметров), события (реакция на действия) и LINQ (язык запросов для красивой работы с данными).

    Делегаты: функции как переменные

    В большинстве языков программирования переменные хранят данные: числа, строки или объекты. Но C# позволяет хранить в переменной... метод.

    Делегат — это тип данных, который хранит ссылку на метод. Это позволяет передавать методы в качестве аргументов другим методам, возвращать их и присваивать переменным.

    Зачем это нужно?

    Представьте, что у вас есть метод для сортировки списка товаров. Но критерии сортировки могут меняться: сегодня мы сортируем по цене, завтра — по названию, послезавтра — по рейтингу. Вместо того чтобы писать три разных метода сортировки, мы пишем один, но передаем в него «логику сравнения» через делегат.

    Объявление и использование

    csharp // Func принимает два double и возвращает double Func<double, double, double> calculateDiscount = (price, discount) => price * (1 - discount / 100);

    double result = calculateDiscount(1000, 20); // 800 csharp class User { public string Name { get; set; } public int Age { get; set; } }

    List<User> users = new List<User> { new User { Name = "Алиса", Age = 25 }, new User { Name = "Боб", Age = 17 }, new User { Name = "Чарли", Age = 30 }, new User { Name = "Дэйв", Age = 15 } }; csharp // Найдем всех совершеннолетних var adults = users.Where(u => u.Age >= 18); // Результат: Алиса, Чарли csharp var names = users.Select(u => u.Name); // Результат: "Алиса", "Боб", "Чарли", "Дэйв" (список строк) csharp var sortedByAge = users.OrderBy(u => u.Age); // Результат: Дэйв (15), Боб (17), Алиса (25), Чарли (30) csharp var result = users .Where(u => u.Age >= 18) // 1. Фильтруем .OrderBy(u => u.Name) // 2. Сортируем .Select(u => u.Name) // 3. Берем только имена .ToList(); // 4. Превращаем результат обратно в List ``

    Отложенное выполнение (Deferred Execution)

    Важно понимать одну концепцию: запросы LINQ не выполняются в момент их написания.

    Когда вы пишете users.Where(...), фильтрация не происходит мгновенно. Создается лишь «инструкция» запроса. Реальное выполнение происходит только тогда, когда вы начинаете перебирать результат (например, в цикле foreach) или вызываете методы принудительного выполнения, такие как ToList(), ToArray(), Count()`.

    Это позволяет строить сложные запросы без потери производительности, так как данные обрабатываются по мере необходимости.

    Заключение

    Сегодня мы сделали огромный шаг вперед в освоении C#. Мы узнали:

  • Делегаты позволяют передавать методы как параметры, делая код гибким.
  • События позволяют объектам общаться, не зная жестко друг о друге (слабая связность).
  • LINQ превращает рутинную работу с циклами и условиями в элегантные декларативные запросы.
  • Эти инструменты — стандарт де-факто в мире .NET. В следующей статье мы рассмотрим работу с файловой системой и потоками ввода-вывода, чтобы наши программы могли сохранять результаты своей работы.

    5. Асинхронное программирование (async/await) и управление ресурсами

    Асинхронное программирование (async/await) и управление ресурсами

    В предыдущих статьях мы научились создавать классы, работать с коллекциями и использовать LINQ для обработки данных. Наши программы работали быстро и четко, но у них был один существенный недостаток: они выполнялись синхронно.

    Представьте, что вы пришли в кафе. Если бы кафе работало синхронно, официант принял бы ваш заказ, пошел на кухню, ждал бы там 20 минут, пока повар приготовит еду, принес бы её вам, и только потом подошел бы к следующему клиенту. Очередь двигалась бы невыносимо медленно.

    В реальной жизни (и в хороших программах) процессы происходят асинхронно. Официант отдает заказ на кухню и сразу идет обслуживать других гостей. Когда еда готова, его уведомляют, и он приносит заказ.

    Сегодня мы разберем, как реализовать такой подход в C# с помощью ключевых слов async и await, а также узнаем, как правильно убирать за собой «мусор» в памяти.

    Проблема блокировки потока

    Когда вы запускаете приложение, операционная система выделяет для него поток (Thread). В простых программах это один главный поток. Если в этом потоке вы решите скачать большой файл из интернета синхронно, то на время скачивания программа «зависнет». Интерфейс перестанет реагировать на нажатия, а окно может побелеть с надписью «Не отвечает».

    Почему так происходит? Потому что процессор занят ожиданием ответа от сети и не может отрисовывать интерфейс.

    Асинхронность в C#

    C# предлагает элегантное решение этой проблемы — паттерн TAP (Task-based Asynchronous Pattern). В центре этого паттерна находятся два ключевых слова: async и await.

    Ключевые слова async и await

    * async: Указывается в заголовке метода. Этот модификатор говорит компилятору, что внутри метода будет использоваться асинхронность. * await: Ставится перед вызовом асинхронной операции. Оно говорит: «Подожди завершения этой задачи, но не блокируй поток. Пусть поток займется чем-то другим, пока мы ждем».

    !Сравнение блокирующего синхронного и неблокирующего асинхронного выполнения.

    Класс Task

    Асинхронные методы не возвращают результат мгновенно. Вместо этого они возвращают «обещание» выполнить работу в будущем. В C# это обещание представлено классом Task.

    * Task: Аналог void для асинхронных методов. Означает, что метод выполнит действие, но не вернет значения. * Task<T>: Аналог возврата значения типа T. Например, Task<int> означает, что метод вернет целое число, когда закончит работу.

    Пример кода

    Давайте посмотрим, как превратить синхронный метод в асинхронный.

    Синхронный вариант (плохо для длительных операций):

    Асинхронный вариант (хорошо):

    Когда выполнение доходит до await Task.Delay(5000), метод DownloadDataAsync как бы «ставится на паузу». Поток, который его выполнял, освобождается и возвращается к другим делам (например, реагирует на клики мыши). Через 5 секунд, когда таймер сработает, система найдет свободный поток и продолжит выполнение метода со следующей строки.

    Возврат значения

    Если нам нужно вернуть результат, мы используем Task<T>:

    Математика производительности

    Асинхронность не всегда делает выполнение одной конкретной задачи быстрее. Часто она даже добавляет небольшие накладные расходы. Её главная цель — пропускная способность и отзывчивость.

    Рассмотрим формулу общего времени выполнения для последовательных задач:

    Где — общее время выполнения, — количество задач, а — время выполнения -й задачи. В синхронном режиме мы обязаны ждать завершения каждой задачи.

    В асинхронном режиме (при параллельном запуске нескольких задач) идеальное время стремится к:

    Где — время выполнения при асинхронном запуске, а — функция выбора максимального времени из набора задач. Это означает, что общее время будет ограничено только самой длительной операцией, так как остальные могут выполняться параллельно ожиданию.

    Важные правила async/await

  • Избегайте async void. Используйте async void только для обработчиков событий (например, нажатие кнопки: private async void Button_Click). Во всех остальных случаях используйте async Task. Если в методе async void произойдет ошибка, она «уронит» все приложение, и вы не сможете её перехватить.
  • Асинхронность заразна. Если вы используете await внутри метода, этот метод тоже должен стать async, и тот, кто его вызывает, тоже должен использовать await. Это распространяется вверх по цепочке вызовов.
  • Именование. Принято добавлять суффикс Async к названиям асинхронных методов (например, GetDataAsync), чтобы программист сразу видел, что этот метод нужно ожидать.
  • Управление ресурсами

    Вторая важная тема этой статьи — работа с памятью и ресурсами. В C# разработчику живется легче, чем в C++, благодаря Garbage Collector (GC) — сборщику мусора.

    Управляемые ресурсы (Managed Resources)

    Это объекты, созданные внутри .NET: классы, строки, массивы. Вам не нужно удалять их вручную. Сборщик мусора периодически проверяет память, находит объекты, на которые больше никто не ссылается, и удаляет их.

    Неуправляемые ресурсы (Unmanaged Resources)

    Однако существуют ресурсы, о которых GC ничего не знает. Это ресурсы операционной системы:

    * Открытые файлы * Соединения с базой данных * Сетевые сокеты * Графические дескрипторы

    Если вы откроете файл и забудете его закрыть, GC может удалить объект, который с ним работал, но сам файл останется заблокированным операционной системой. Это приведет к утечке ресурсов.

    Интерфейс IDisposable

    Для работы с такими ресурсами в C# существует интерфейс IDisposable. Он содержит всего один метод:

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

    Конструкция using

    Вызывать Dispose() вручную неудобно и опасно. Если перед вызовом произойдет ошибка (исключение), метод Dispose() не выполнится, и ресурс утечет.

    Раньше приходилось писать громоздкие конструкции try-finally. Сейчас в C# есть оператор using, который гарантирует вызов Dispose() автоматически, даже если произошла ошибка.

    Современный способ с using:

    Существует еще более короткая запись (начиная с C# 8.0), без фигурных скобок. Ресурс освободится, когда выполнение выйдет за пределы текущего метода:

    Заключение

    Сегодня мы рассмотрели две фундаментальные концепции профессиональной разработки:

  • Асинхронность (async/await) позволяет создавать отзывчивые приложения, которые не блокируют ресурсы во время ожидания длительных операций.
  • Управление ресурсами (IDisposable, using) гарантирует, что ваше приложение не будет «течь» и блокировать файлы или сетевые порты.
  • Эти знания переводят вас из разряда новичков, пишущих простые скрипты, в лигу разработчиков, способных создавать надежные и производительные системы. В следующей статье мы объединим все полученные знания и поговорим о том, как правильно обрабатывать ошибки и исключения в сложных сценариях.