Уявіть, що ви створюєте додаток для онлайн-магазину. Користувач додає товар до кошика, і програма має прийняти рішення: чи достатньо товару на складі? Якщо так — додати в кошик, якщо ні — показати повідомлення про відсутність. Далі програма має перевірити всі товари в кошику, обчислити загальну суму з урахуванням знижок та податків. Кожна з цих дій потребує потоку керування (control flow) — механізму, який визначає, які інструкції виконуються, в якому порядку та за яких умов.
Потік керування — це фундаментальна концепція програмування, яка дозволяє створювати динамічні та інтелектуальні програми. Без нього код виконувався б лише послідовно, рядок за рядком, що унеможливлювало б будь-яку складну логіку.
У цьому розділі ви опануєте:
if, else, switch, тернарний оператор)for, foreach, while, do-while)break, continue, return)switch виразах для елегантної обробки складних умовПеред вивченням цього розділу рекомендується ознайомитися з:
Умовні оператори дозволяють програмі приймати рішення на основі певних умов. Це фундамент логіки будь-якої програми.
ifНайпростіший спосіб виконати код залежно від умови — це оператор if.
Синтаксис:
if (умова)
{
// Код, що виконується, якщо умова істинна
}
Приклад:
int temperature = 25;
if (temperature > 20)
{
Console.WriteLine("Сьогодні тепло!");
}
{} навіть для однорядкових блоків коду. Це покращує читабельність та запобігає помилкам при подальшому редагуванні коду.if-elseДля обробки альтернативного сценарію використовується else:
int age = 16;
if (age >= 18)
{
Console.WriteLine("Ви можете голосувати.");
}
else
{
Console.WriteLine("Ви ще не досягли віку для голосування.");
}
else ifДля перевірки кількох умов послідовно:
int score = 85;
if (score >= 90)
{
Console.WriteLine("Оцінка: Відмінно");
}
else if (score >= 75)
{
Console.WriteLine("Оцінка: Добре");
}
else if (score >= 60)
{
Console.WriteLine("Оцінка: Задовільно");
}
else
{
Console.WriteLine("Оцінка: Незадовільно");
}
Умовні оператори можна вкладати один в одного:
int temperature = 22;
bool isRaining = false;
if (temperature > 20)
{
if (isRaining)
{
Console.WriteLine("Тепло, але дощить. Візьміть парасольку!");
}
else
{
Console.WriteLine("Чудова погода для прогулянки!");
}
}
else
{
Console.WriteLine("Холодно. Одягніться тепліше.");
}
Тернарний оператор ?: — це скорочена форма if-else, яка повертає значення:
Синтаксис:
умова ? значення_якщо_істина : значення_якщо_хиба
Приклад:
int age = 20;
string status = age >= 18 ? "Дорослий" : "Неповнолітній";
Console.WriteLine(status); // Виведе: Дорослий
int number = 42;
string result;
if (number % 2 == 0)
{
result = "Парне";
}
else
{
result = "Непарне";
}
int number = 42;
string result = number % 2 == 0 ? "Парне" : "Непарне";
if-else для покращення читабельності.switchОператор switch використовується для багатоваріантного вибору на основі значення виразу.
Синтаксис:
switch (вираз)
{
case значення1:
// Код для значення1
break;
case значення2:
// Код для значення2
break;
default:
// Код за замовчуванням
break;
}
Приклад:
int dayOfWeek = 3;
switch (dayOfWeek)
{
case 1:
Console.WriteLine("Понеділок");
break;
case 2:
Console.WriteLine("Вівторок");
break;
case 3:
Console.WriteLine("Середа");
break;
case 4:
Console.WriteLine("Четвер");
break;
case 5:
Console.WriteLine("П'ятниця");
break;
case 6:
Console.WriteLine("Субота");
break;
case 7:
Console.WriteLine("Неділя");
break;
default:
Console.WriteLine("Некоректний день тижня");
break;
}
caseМожна об'єднувати кілька міток case для виконання одного блоку коду:
int dayOfWeek = 6;
switch (dayOfWeek)
{
case 1:
case 2:
case 3:
case 4:
case 5:
Console.WriteLine("Робочий день");
break;
case 6:
case 7:
Console.WriteLine("Вихідний");
break;
default:
Console.WriteLine("Некоректний день");
break;
}
case, як у деяких інших мовах. Кожен case має закінчуватися оператором break, return, goto або throw.| Конструкція | Коли використовувати | Переваги | Недоліки |
|---|---|---|---|
if-else | Умови на основі булевих виразів, складна логіка | Універсальність, зрозумілість | Багато коду для простих випадків |
| Тернарний оператор | Прості умови з поверненням значення | Компактність | Погана читабельність при вкладанні |
switch | Перевірка одного виразу на множину значень | Читабельність для багатьох варіантів | Обмежений тип виразу (до C# 7) |
switch вираз | Як switch, але з поверненням значення | Компактність, pattern matching | Потребує C# 8.0+ |
Цикли дозволяють повторювати блок коду кілька разів. Це критично важливо для обробки колекцій, повторення дій та реалізації алгоритмів.
forЦикл for використовується, коли кількість ітерацій відома заздалегідь.
Синтаксис:
for (ініціалізація; умова; ітератор)
{
// Код, що повторюється
}
Компоненти:
Приклад:
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Ітерація: {i}");
}
// Виведе:
// Ітерація: 0
// Ітерація: 1
// Ітерація: 2
// Ітерація: 3
// Ітерація: 4
forМожна використовувати кілька змінних:
for (int i = 0, j = 10; i < j; i++, j--)
{
Console.WriteLine($"i = {i}, j = {j}");
}
for ідеально підходить для роботи з масивами, коли потрібен доступ до індексу елемента.foreachЦикл foreach використовується для ітерації по колекціях без необхідності маніпулювати індексом.
Синтаксис:
foreach (тип змінна in колекція)
{
// Код, що використовує змінну
}
Приклад:
string[] fruits = { "Яблуко", "Банан", "Апельсин", "Груша" };
foreach (string fruit in fruits)
{
Console.WriteLine($"Фрукт: {fruit}");
}
varКомпілятор може автоматично визначити тип:
var numbers = new[] { 1, 2, 3, 4, 5 };
foreach (var number in numbers)
{
Console.WriteLine(number * 2);
}
foreach працює з будь-яким типом, що реалізує інтерфейс IEnumerable або IEnumerable<T>. Елементи колекції доступні лише для читання всередині циклу.int[] numbers = { 10, 20, 30, 40, 50 };
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine($"Індекс {i}: {numbers[i]}");
}
int[] numbers = { 10, 20, 30, 40, 50 };
foreach (int number in numbers)
{
Console.WriteLine($"Значення: {number}");
}
whileЦикл while виконується доки умова істинна. Він використовується, коли кількість ітерацій невідома наперед.
Синтаксис:
while (умова)
{
// Код, що повторюється
}
Приклад:
int count = 0;
while (count < 5)
{
Console.WriteLine($"Лічильник: {count}");
count++;
}
while колись стане хибною, інакше програма зависне.// НЕ РОБІТЬ ТАК!
while (true)
{
// Цей код виконуватиметься вічно
}
do-whileЦикл do-while схожий на while, але гарантує виконання тіла циклу принаймні один раз, оскільки умова перевіряється після виконання блоку коду.
Синтаксис:
do
{
// Код, що повторюється
} while (умова);
Приклад:
int userInput;
do
{
Console.Write("Введіть число більше 0: ");
userInput = int.Parse(Console.ReadLine());
} while (userInput <= 0);
Console.WriteLine($"Ви ввели: {userInput}");
int count = 10;
while (count < 5)
{
Console.WriteLine(count);
count++;
}
// Код не виконається жодного разу
int count = 10;
do
{
Console.WriteLine(count);
count++;
} while (count < 5);
// Виведе: 10 (один раз)
| Тип циклу | Коли використовувати | Особливості |
|---|---|---|
for | Кількість ітерацій відома, потрібен індекс | Компактний синтаксис, ініціалізація + умова + ітератор |
foreach | Ітерація по колекції без індексу | Найпростіший синтаксис, лише для читання |
while | Кількість ітерацій невідома, перевірка в початку | Умова перевіряється перед виконанням |
do-while | Те саме що while, але потрібне мінімум 1 виконання | Умова перевіряється після виконання |
Оператори переходу (jump statements) дозволяють змінювати нормальний потік виконання програми. Вони можуть переривати цикли, пропускати ітерації або виходити з методів.
breakОператор break припиняє виконання найближчого циклу або оператора switch.
Використання в циклі:
for (int i = 0; i < 10; i++)
{
if (i == 5)
{
break; // Вихід з циклу при i = 5
}
Console.WriteLine(i);
}
// Виведе: 0 1 2 3 4
Використання у вкладених циклах:
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (j == 1)
{
break; // Виходить лише з внутрішнього циклу
}
Console.WriteLine($"i={i}, j={j}");
}
}
break припиняє лише найближчий цикл або switch. Для виходу з кількох вкладених циклів використовуйте прапорець (flag) або винесіть код у метод з return.continueОператор continue пропускає решту коду в поточній ітерації циклу та переходить до наступної ітерації.
for (int i = 0; i < 10; i++)
{
if (i % 2 == 0)
{
continue; // Пропустити парні числа
}
Console.WriteLine(i);
}
// Виведе: 1 3 5 7 9
Практичний приклад:
string[] files = { "document.txt", "image.png", "data.csv", "photo.jpg", "report.txt" };
foreach (string file in files)
{
if (!file.EndsWith(".txt"))
{
continue; // Пропустити файли, що не є текстовими
}
Console.WriteLine($"Обробка текстового файлу: {file}");
}
// Виведе:
// Обробка текстового файлу: document.txt
// Обробка текстового файлу: report.txt
continue для покращення читабельності коду, щоб уникнути глибокого вкладання умовних операторів у циклах.returnОператор return припиняє виконання методу та повертає керування викликаючому коду. Він може повертати значення або бути порожнім (для методів типу void).
Повернення значення:
int Add(int a, int b)
{
return a + b; // Повертає суму
}
int result = Add(5, 3); // result = 8
Використання для раннього виходу (Guard Clauses):
Guard Clauses (охоронні вирази) — це техніка програмування, яка полягає у використанні раннього повернення (early return) для перевірки умов та виходу з методу при помилкових або небажаних умовах. Ця практика покращує читабельність коду, зменшує вкладеність та робить логіку програми більш зрозумілою.
Guard Clauses діють за принципом "захисту" основної логіки методу від некоректних вхідних даних або станів. Вони розміщуються на початку методу та виконують перевірки, які дозволяють швидко вийти з методу, якщо умови не відповідають вимогам.
if-else блоківДо (з вкладеними умовами):
void ProcessOrder(Order order)
{
if (order != null)
{
if (order.Items.Count > 0)
{
if (order.Customer != null)
{
// Основна логіка обробки замовлення
Console.WriteLine($"Обробка замовлення {order.Id} для клієнта {order.Customer.Name}");
// ... складна логіка обробки
}
else
{
Console.WriteLine("Помилка: клієнт відсутній");
}
}
else
{
Console.WriteLine("Помилка: замовлення порожнє");
}
}
else
{
Console.WriteLine("Помилка: замовлення відсутнє");
}
}
Після (з Guard Clauses):
void ProcessOrder(Order order)
{
// Guard Clauses - перевірка умов на початку методу
if (order == null)
{
Console.WriteLine("Помилка: замовлення відсутнє");
return;
}
if (order.Items.Count == 0)
{
Console.WriteLine("Помилка: замовлення порожнє");
return;
}
if (order.Customer == null)
{
Console.WriteLine("Помилка: клієнт відсутній");
return;
}
// Основна логіка обробки замовлення
Console.WriteLine($"Обробка замовлення {order.Id} для клієнта {order.Customer.Name}");
// ... складна логіка обробки
}
Для більш елегантної реалізації Guard Clauses можна використовувати спеціалізовані бібліотеки, такі як Light.GuardClauses.
Встановлення бібліотеки:
dotnet add package Light.GuardClauses
Приклад з використанням бібліотеки:
using Light.GuardClauses;
public class OrderProcessor
{
private readonly IOrderRepository _orderRepository;
private readonly ICustomerService _customerService;
public OrderProcessor(IOrderRepository orderRepository, ICustomerService customerService)
{
// Guard Clauses для конструктора
_orderRepository = orderRepository.MustNotBeNull();
_customerService = customerService.MustNotBeNull();
}
public void ProcessOrder(Order order)
{
// Guard Clauses для параметрів методу
order.MustNotBeNull();
order.Items.MustNotBeNullOrEmpty();
order.Customer.MustNotBeNull();
// Додаткові перевірки
order.Id.MustNotBeEmpty();
order.TotalAmount.MustBeGreaterThan(0);
// Основна логіка обробки
var customer = _customerService.GetCustomer(order.Customer.Id);
customer.MustNotBeNull();
_orderRepository.Save(order);
Console.WriteLine($"Замовлення {order.Id} успішно оброблено");
}
}
public string ValidateUserInput(string input, int minLength, int maxLength)
{
// Guard Clauses з використанням switch виразів
return input switch
{
null => "Вхідне значення не може бути null",
{ Length: 0 } => "Вхідне значення не може бути порожнім",
string s when s.Length < minLength => $"Довжина повинна бути не менше {minLength}",
string s when s.Length > maxLength => $"Довжина повинна бути не більше {maxLength}",
string s when s.Trim().Length == 0 => "Вхідне значення не може містити тільки пробіли",
_ => "Валідація пройшла успішно"
};
}
public async Task<OrderResult> ProcessOrderAsync(Order order, CancellationToken cancellationToken = default)
{
// Guard Clauses для асинхронних методів
if (order == null)
throw new ArgumentNullException(nameof(order), "Замовлення не може бути null");
if (order.Items == null || !order.Items.Any())
throw new ArgumentException("Замовлення повинно містити хоча б один товар", nameof(order));
if (cancellationToken.IsCancellationRequested)
throw new OperationCanceledException("Операція була скасована");
try
{
// Асинхронна обробка замовлення
var validationResult = await ValidateOrderAsync(order, cancellationToken);
if (!validationResult.IsValid)
{
return new OrderResult { Success = false, Error = validationResult.ErrorMessage };
}
var processedOrder = await SaveOrderAsync(order, cancellationToken);
return new OrderResult { Success = true, OrderId = processedOrder.Id };
}
catch (Exception ex)
{
_logger.LogError(ex, "Помилка під час обробки замовлення");
throw;
}
}
ProcessOrder()
├── if (order != null)
│ └── if (order.Items.Count > 0)
│ └── if (order.Customer != null)
│ └── [Основна логіка] ← Глибока вкладеність
│ └── else
│ └── [Помилка Customer]
│ └── else
│ └── [Помилка Items]
└── else
└── [Помилка order]
ProcessOrder()
├── if (order == null) → return [Помилка order]
├── if (order.Items.Count == 0) → return [Помилка Items]
├── if (order.Customer == null) → return [Помилка Customer]
└── [Основна логіка] ← Пряма лінія виконання
| Аспект | Традиційний підхід | Guard Clauses |
|---|---|---|
| Читабельність | Низька (глибока вкладеність) | Висока (лінійна структура) |
| Підтримка | Складніше (багато рівнів вкладення) | Простіше (очевидна структура) |
| Тестування | Складніше (потрібні складніші тести) | Простіше (кожна умова окремо) |
| Продуктивність | Однакова | Однакова |
| Обсяг коду | Більше | Менше |
Guard Clauses - це потужна техніка, яка допомагає писати чистіший, зрозуміліший та легший у супроводі код. Використовуючи цей підхід, ви:
Ця практика особливо корисна у великих проектах, де важлива підтримка коду та його розуміння командою розробників.
breakПовністю виходить з циклу або switch. Виконання продовжується з наступного оператора після циклу.
for (int i = 0; i < 10; i++)
{
if (i == 5) break;
Console.Write(i + " ");
}
// Виведе: 0 1 2 3 4
continueПропускає решту коду в поточній ітерації, але цикл продовжується. Переходить безпосередньо до наступної ітерації.
for (int i = 0; i < 10; i++)
{
if (i == 5) continue;
Console.Write(i + " ");
}
// Виведе: 0 1 2 3 4 6 7 8 9
returnВиходить з усього методу, незалежно від циклів. Може повертати значення.
int FindFirst(int[] arr, int target)
{
for (int i = 0; i < arr.Length; i++)
{
if (arr[i] == target)
{
return i; // Повертає індекс
}
}
return -1; // Не знайдено
}
Починаючи з C# 8.0, мова отримала потужний функціонал зіставлення шаблонів (pattern matching), який революціонізував роботу з умовною логікою. Switch вирази (switch expressions) — це сучасна альтернатива традиційним операторам switch, яка є більш виразною та компактною.
string GetSeasonName(int month)
{
string season;
switch (month)
{
case 12:
case 1:
case 2:
season = "Зима";
break;
case 3:
case 4:
case 5:
season = "Весна";
break;
case 6:
case 7:
case 8:
season = "Літо";
break;
case 9:
case 10:
case 11:
season = "Осінь";
break;
default:
season = "Невідомо";
break;
}
return season;
}
string GetSeasonName(int month) => month switch
{
12 or 1 or 2 => "Зима",
3 or 4 or 5 => "Весна",
6 or 7 or 8 => "Літо",
9 or 10 or 11 => "Осінь",
_ => "Невідомо"
};
switch, яка повертає значення. Основні відмінності:=> замість : та break=>_ (discard) замість defaultПеревірка на точну відповідність значенню:
string GetDayType(int day) => day switch
{
6 => "Субота",
7 => "Неділя",
_ => "Робочий день"
};
Використання операторів порівняння <, >, <=, >=:
string GetWaterState(int temperature) => temperature switch
{
< 0 => "Лід",
0 => "Точка замерзання",
> 0 and < 100 => "Рідина",
100 => "Точка кипіння",
> 100 => "Пара",
_ => "Невідомий стан"
};
Комбінування шаблонів за допомогою and, or, not:
bool IsWeekday(int day) => day switch
{
>= 1 and <= 5 => true,
6 or 7 => false,
_ => throw new ArgumentException("Некоректний день")
};
string ClassifyAge(int age) => age switch
{
< 0 => "Некоректний вік",
>= 0 and < 13 => "Дитина",
>= 13 and < 18 => "Підліток",
>= 18 and < 65 => "Дорослий",
>= 65 => "Пенсіонер"
};
Перевірка властивостей об'єктів:
public record Person(string Name, int Age);
string GetCategory(Person person) => person switch
{
{ Age: < 18 } => "Неповнолітній",
{ Age: >= 18, Name: "Admin" } => "Адміністратор",
{ Age: >= 18 } => "Дорослий користувач",
_ => "Невідомо"
};
Використання деконструкції для перевірки кортежів або типів з деконструктором:
public record Point(int X, int Y);
string GetQuadrant(Point point) => point switch
{
(0, 0) => "Початок координат",
(> 0, > 0) => "Перший квадрант",
(< 0, > 0) => "Другий квадрант",
(< 0, < 0) => "Третій квадрант",
(> 0, < 0) => "Четвертий квадрант",
(_, 0) => "На осі X",
(0, _) => "На осі Y"
};
Використання _ для ігнорування значень:
string GetInfo((string name, int age, string city) person) => person switch
{
("John", _, _) => "Це John, незалежно від віку та міста",
(_, > 65, _) => "Пенсіонер",
(_, _, "Kyiv") => "Житель Києва",
_ => "Інший користувач"
};
whenДодаткові умови для більш точного зіставлення:
decimal CalculateDiscount(int age, bool isMember) => (age, isMember) switch
{
( < 18, _) => 0.20m, // 20% для дітей
( >= 65, _) => 0.15m, // 15% для пенсіонерів
(_, true) when age >= 18 && age < 65 => 0.10m, // 10% для членів
_ => 0m // Без знижки
};
Складніший приклад з when:
public record Order(int ItemCount, decimal TotalPrice, bool IsPriority);
string GetShippingMethod(Order order) => order switch
{
{ IsPriority: true } => "Експрес доставка",
{ ItemCount: > 10, TotalPrice: > 1000 } => "Вантажна доставка",
{ TotalPrice: > 500 } when order.ItemCount <= 5 => "Стандартна доставка з упаковкою",
{ ItemCount: <= 3 } => "Кур'єрська служба",
_ => "Звичайна пошта"
};
Компілятор C# перевіряє, чи охоплюють шаблони всі можливі значення. Якщо ні, виникне попередження:
enum TrafficLight { Red, Yellow, Green }
// Попередження CS8509: Не всі значення охоплені
string GetAction(TrafficLight light) => light switch
{
TrafficLight.Red => "Стоп",
TrafficLight.Green => "Рух"
// Відсутній Yellow!
};
// Правильна версія:
string GetActionCorrect(TrafficLight light) => light switch
{
TrafficLight.Red => "Стоп",
TrafficLight.Yellow => "Підготуватися",
TrafficLight.Green => "Рух",
_ => throw new ArgumentException("Невідомий сигнал")
};
| Аспект | Традиційний switch | Switch вираз |
|---|---|---|
| Синтаксис | Оператор з case, :, break | Вираз з => |
| Повернення значення | Потрібна окрема змінна | Пряме повернення |
| Компактність | Багатослівний | Лаконічний |
| Pattern matching | Обмежений (C# 7+) | Повна підтримка |
| Перевірка вичерпності | Ні | Так (попередження) |
| Версія C# | Всі версії | C# 8.0+ |
public record HttpResponse(int StatusCode, string Body);
string HandleResponse(HttpResponse response) => response switch
{
{ StatusCode: 200, Body: not null } => $"Успіх: {response.Body}",
{ StatusCode: 200, Body: null } => "Успіх, але немає даних",
{ StatusCode: >= 200 and < 300 } => "Успішна відповідь",
{ StatusCode: 400 } => "Поганий запит",
{ StatusCode: 401 } => "Не авторизовано",
{ StatusCode: 404 } => "Не знайдено",
{ StatusCode: >= 400 and < 500 } => "Помилка клієнта",
{ StatusCode: >= 500 } => "Помилка сервера",
_ => "Невідомий статус"
};
void ProcessUser(User user)
{
if (user != null)
{
if (user.IsActive)
{
if (user.HasPermission)
{
// Основна логіка
}
}
}
}
void ProcessUser(User user)
{
if (user == null) return;
if (!user.IsActive) return;
if (!user.HasPermission) return;
// Основна логіка
}
for — коли потрібен індекс або конкретна кількість ітераційforeach — для простої ітерації по колекціїwhile — коли кількість ітерацій невідомаdo-while — коли потрібне мінімум одне виконанняswitch, коли це доцільно.foreach (var x in items)
{
// Що таке x?
}
foreach (var product in products)
{
// Зрозуміло, що це продукт
}
// Помилка: лічильник не змінюється
int i = 0;
while (i < 10)
{
Console.WriteLine(i);
// Забули i++
}
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Помилка: зміна списку під час ітерації
foreach (var num in numbers)
{
if (num % 2 == 0)
{
numbers.Remove(num); // InvalidOperationException!
}
}
for в зворотному порядку або створіть нову колекцію.// Правильно: ітерація у зворотному порядку
for (int i = numbers.Count - 1; i >= 0; i--)
{
if (numbers[i] % 2 == 0)
{
numbers.RemoveAt(i);
}
}
break у switch// Помилка компіляції в C#
switch (value)
{
case 1:
Console.WriteLine("One");
// Забули break - помилка компіляції!
case 2:
Console.WriteLine("Two");
break;
}
break, return або goto в кінці кожного case.continue та break// Помилка: плутанина між continue та break
for (int i = 0; i < 10; i++)
{
if (i == 5)
{
continue; // Пропускає лише 5
}
Console.WriteLine(i);
}
// Очікували вихід на 5, але отримали пропуск
break — виходить з циклуcontinue — пропускає поточну ітераціюСимптом: Попередження компілятора про завжди істинну або хибну умову.
int x = 5;
if (x = 5) // Помилка: присвоєння замість порівняння
{
// ...
}
Вирішення:
int x = 5;
if (x == 5) // Правильно: оператор порівняння
{
// ...
}
Симптом: Код після return, break або безумовного переходу ніколи не виконується.
int GetValue()
{
return 42;
Console.WriteLine("Це ніколи не виконається"); // Попередження
}
Вирішення: Видаліть недосяжний код або перегляньте логіку.
Симптом: Компілятор повідомляє, що метод не завжди повертає значення.
int GetSign(int number)
{
if (number > 0)
{
return 1;
}
else if (number < 0)
{
return -1;
}
// Що якщо number == 0?
}
Вирішення:
int GetSign(int number)
{
if (number > 0) return 1;
if (number < 0) return -1;
return 0; // Обробка всіх випадків
}
Симптом: Цикл виконується на одну ітерацію більше або менше, ніж очікувалось.
int[] arr = { 1, 2, 3, 4, 5 };
// Помилка: IndexOutOfRangeException
for (int i = 0; i <= arr.Length; i++) // Повинно бути <, а не <=
{
Console.WriteLine(arr[i]);
}
Вирішення:
for (int i = 0; i < arr.Length; i++) // Правильно
{
Console.WriteLine(arr[i]);
}
Напишіть програму, яка запитує у користувача ціле число та визначає, чи є воно парним або непарним, використовуючи оператор if-else.
%. Якщо number % 2 == 0, число парне.Console.Write("Введіть ціле число: ");
int number = int.Parse(Console.ReadLine());
if (number % 2 == 0)
{
Console.WriteLine($"{number} є парним числом.");
}
else
{
Console.WriteLine($"{number} є непарним числом.");
}
Напишіть програму, яка обчислює суму всіх цілих чисел від 1 до N (включно) за допомогою циклу for.
Console.Write("Введіть число N: ");
int n = int.Parse(Console.ReadLine());
int sum = 0;
for (int i = 1; i <= n; i++)
{
sum += i;
}
Console.WriteLine($"Сума чисел від 1 до {n} = {sum}");
Створіть масив з 5 елементів та виведіть всі його елементи за допомогою циклу foreach.
int[] numbers = { 10, 20, 30, 40, 50 };
Console.WriteLine("Елементи масиву:");
foreach (int number in numbers)
{
Console.WriteLine(number);
}
Створіть простий калькулятор, який приймає два числа та оператор (+, -, *, /) і виконує відповідну операцію, використовуючи switch вираз.
switch вираз для визначення операції на основі введеного оператора.Console.Write("Введіть перше число: ");
double num1 = double.Parse(Console.ReadLine());
Console.Write("Введіть оператор (+, -, *, /): ");
string op = Console.ReadLine();
Console.Write("Введіть друге число: ");
double num2 = double.Parse(Console.ReadLine());
double result = op switch
{
"+" => num1 + num2,
"-" => num1 - num2,
"*" => num1 * num2,
"/" when num2 != 0 => num1 / num2,
"/" => throw new DivideByZeroException("Ділення на нуль!"),
_ => throw new InvalidOperationException("Невідомий оператор")
};
Console.WriteLine($"Результат: {num1} {op} {num2} = {result}");
Напишіть програму, яка виводить таблицю множення для числа від 1 до 10, використовуючи вкладені цикли for.
for (int i = 1; i <= 10; i++)
{
for (int j = 1; j <= 10; j++)
{
Console.Write($"{i * j,4}"); // Форматування з вирівнюванням
}
Console.WriteLine(); // Новий рядок після кожного ряду
}
Напишіть програму, яка знаходить всі прості числа від 2 до N. Використовуйте оператори continue або break для оптимізації.
Console.Write("Введіть число N: ");
int n = int.Parse(Console.ReadLine());
Console.WriteLine($"Прості числа від 2 до {n}:");
for (int i = 2; i <= n; i++)
{
bool isPrime = true;
for (int j = 2; j * j <= i; j++)
{
if (i % j == 0)
{
isPrime = false;
break; // Знайшли дільник - не просте
}
}
if (isPrime)
{
Console.Write(i + " ");
}
}
Console.WriteLine();
Створіть ієрархію класів для геометричних фігур (коло, прямокутник, трикутник) та напишіть метод, який використовує pattern matching для обчислення площі.
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
public record Triangle(double Base, double Height) : Shape;
public class ShapeCalculator
{
public static double CalculateArea(Shape shape) => shape switch
{
Circle { Radius: var r } => Math.PI * r * r,
Rectangle { Width: var w, Height: var h } => w * h,
Triangle { Base: var b, Height: var h } => 0.5 * b * h,
_ => throw new ArgumentException("Невідомий тип фігури")
};
public static string GetDescription(Shape shape) => shape switch
{
Circle { Radius: < 5 } => "Маленьке коло",
Circle { Radius: >= 5 and < 10 } => "Середнє коло",
Circle { Radius: >= 10 } => "Велике коло",
Rectangle { Width: var w, Height: var h } when w == h => "Квадрат",
Rectangle => "Прямокутник",
Triangle => "Трикутник",
_ => "Невідома фігура"
};
}
// Використання:
var shapes = new Shape[]
{
new Circle(5),
new Rectangle(4, 6),
new Triangle(3, 4),
new Rectangle(5, 5)
};
foreach (var shape in shapes)
{
double area = ShapeCalculator.CalculateArea(shape);
string description = ShapeCalculator.GetDescription(shape);
Console.WriteLine($"{description}: Площа = {area:F2}");
}
Напишіть метод бінарного пошуку в відсортованому масиві. Використовуйте цикл while та оператори переходу для ефективного пошуку.
public class BinarySearch
{
public static int Search(int[] sortedArray, int target)
{
int left = 0;
int right = sortedArray.Length - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (sortedArray[mid] == target)
{
return mid; // Знайдено
}
if (sortedArray[mid] < target)
{
left = mid + 1; // Шукаємо в правій половині
}
else
{
right = mid - 1; // Шукаємо в лівій половині
}
}
return -1; // Не знайдено
}
}
// Використання:
int[] numbers = { 2, 5, 8, 12, 16, 23, 38, 45, 56, 67, 78 };
int target = 23;
int index = BinarySearch.Search(numbers, target);
if (index != -1)
{
Console.WriteLine($"Елемент {target} знайдено на індексі {index}");
}
else
{
Console.WriteLine($"Елемент {target} не знайдено");
}
Створіть систему обробки HTTP запитів з використанням pattern matching для маршрутизації та обробки різних типів запитів.
public record HttpRequest(string Method, string Path, Dictionary<string, string> Headers);
public class RequestHandler
{
public static string HandleRequest(HttpRequest request) => request switch
{
// API ендпоінти
{ Method: "GET", Path: "/api/users" } => GetAllUsers(),
{ Method: "GET", Path: var p } when p.StartsWith("/api/users/")
=> GetUserById(ExtractId(p)),
{ Method: "POST", Path: "/api/users" } => CreateUser(),
{ Method: "PUT", Path: var p } when p.StartsWith("/api/users/")
=> UpdateUser(ExtractId(p)),
{ Method: "DELETE", Path: var p } when p.StartsWith("/api/users/")
=> DeleteUser(ExtractId(p)),
// Статичні ресурси
{ Path: var p } when p.EndsWith(".css") => ServeStaticFile(p, "text/css"),
{ Path: var p } when p.EndsWith(".js") => ServeStaticFile(p, "application/javascript"),
{ Path: var p } when p.EndsWith(".html") => ServeStaticFile(p, "text/html"),
// Особливі випадки
{ Headers: var h } when h.ContainsKey("Authorization") && !IsAuthorized(h)
=> "401 Unauthorized",
{ Method: "OPTIONS" } => HandleOptionsRequest(),
// За замовчуванням
_ => "404 Not Found"
};
private static string ExtractId(string path) => path.Split('/').Last();
private static string GetAllUsers() => "Список всіх користувачів";
private static string GetUserById(string id) => $"Користувач з ID: {id}";
private static string CreateUser() => "Створено нового користувача";
private static string UpdateUser(string id) => $"Оновлено користувача {id}";
private static string DeleteUser(string id) => $"Видалено користувача {id}";
private static string ServeStaticFile(string path, string contentType)
=> $"Serving {path} as {contentType}";
private static string HandleOptionsRequest() => "CORS headers sent";
private static bool IsAuthorized(Dictionary<string, string> headers) => true;
}
// Використання:
var requests = new[]
{
new HttpRequest("GET", "/api/users", new()),
new HttpRequest("GET", "/api/users/123", new()),
new HttpRequest("POST", "/api/users", new()),
new HttpRequest("GET", "/styles/main.css", new()),
new HttpRequest("GET", "/unknown", new())
};
foreach (var request in requests)
{
string response = RequestHandler.HandleRequest(request);
Console.WriteLine($"{request.Method} {request.Path} => {response}");
}
У цьому розділі ви вивчили потік керування (control flow) — фундаментальні механізми, які дозволяють програмам приймати рішення та повторювати дії:
Умовні оператори:
if, else, else if для базових умовних розгалужень? : для компактних умовних виразівswitch для множинного виборуЦикли:
for — для фіксованої кількості ітерацій з доступом до індексуforeach — для зручної ітерації по колекціяхwhile — для циклів з попередньою перевіркою умовиdo-while — для циклів з гарантованим першим виконаннямОператори переходу:
break — вихід з циклу або switchcontinue — пропуск поточної ітерації циклуreturn — вихід з методу з можливим поверненням значенняPattern Matching:
when для додаткових умовОпанування потоку керування — це ключ до написання ефективної та елегантної логіки програми. Практикуйте різні підходи, обирайте найбільш читабельні рішення та використовуйте сучасні можливості C# для створення чистого коду!
