Основи Відлагодження
Основи Відлагодження (Debugging Basics)
Вступ та Контекст
Уявіть собі ситуацію: ваша програма компілюється без помилок, але при виконанні видає неочікуваний результат. Користувачі скаржаться, що їхні замовлення обробляються з неправильними цінами. Де саме виникає проблема? Яка змінна містить некоректне значення? У якій послідовності викликаються методи?
Саме для вирішення таких задач існує відлагодження (debugging) — процес виявлення та усунення помилок у коді. Відлагоджувач (debugger) — це потужний інструмент, який дозволяє зупиняти виконання програми, інспектувати стан змінних, покроково виконувати код та аналізувати потік виконання.
Що ви отримаєте
Після вивчення цього розділу ви зможете:
- Ефективно використовувати breakpoints (точки зупинки) для аналізу коду
- Інспектувати змінні через Locals, Watch та Call Stack вікна
- Навігувати по коду за допомогою Step Into, Step Over та Step Out
- Виконувати код "на льоту" через Immediate Window
- Швидко діагностувати та виправляти типові помилки
Передумови
Для роботи з цим розділом вам необхідно:
- Встановлене середовище розробки (Visual Studio або Visual Studio Code з C# Dev Kit)
- Базове розуміння синтаксису C# (змінні, методи, класи)
- Знання про типи даних та control flow
Фундаментальні Концепції
Що таке відлагодження?
Відлагодження (Debugging) — це систематичний процес виявлення, локалізації та усунення помилок (bugs) у програмному коді. Це не просто "виправлення того, що не працює", а наукова методика аналізу поведінки програми на різних рівнях абстракції.
Життєвий цикл відлагодження
Visual Studio Debugger
Visual Studio надає один з найпотужніших відлагоджувачів для .NET розробки. Він інтегрований безпосередньо в IDE та підтримує:
- Interactive debugging: Зупинка виконання в будь-який момент
- Data inspection: Перегляд та модифікація змінних у реальному часі
- Navigation: Покрокове виконання коду з повним контролем
- Expression evaluation: Виконання C# виразів під час паузи
Breakpoints (Точки Зупинки)
Breakpoint — це маркер у коді, який інструктує відлагоджувач призупинити виконання програми у вказаному місці. Це найбазовіший та найважливіший інструмент відлагодження.
Встановлення Breakpoints
Три способи встановити breakpoint:
- Клік на полі (gutter): Клацніть лівою кнопкою миші на сірому полі ліворуч від номера рядка
- Клавіатурою: Поставте курсор на потрібний рядок та натисніть
F9 - Меню:
Debug→Toggle Breakpoint
Breakpoint візуально відображається як червоне коло на полі редактора.

::
Встановлення breakpoint у VS Code:
- Клацніть на лівому полі редактора (ліворуч від номера рядка)
- Або натисніть
F9на потрібному рядку
Breakpoint відображається як червона крапка.

::
Керування Breakpoints
using System;
class Program
{
static void Main()
{
int sum = 0; // ← Breakpoint тут (рядок 7)
for (int i = 1; i <= 5; i++)
{
sum += i; // ← Breakpoint тут (рядок 11)
Console.WriteLine($"Ітерація {i}: sum = {sum}");
}
Console.WriteLine($"Підсумок: {sum}"); // ← Breakpoint тут (рядок 15)
}
}
Операції з breakpoints:
| Дія | Visual Studio | VS Code |
|---|---|---|
| Встановити/Видалити | F9 | F9 |
| Видалити всі | Ctrl+Shift+F9 | Через Debug панель |
| Вимкнути (не видаляючи) | Правий клік → Disable | Правий клік → Disable |
| Список всіх breakpoints | Ctrl+Alt+B або Debug → Windows → Breakpoints | Debug панель → Breakpoints |
Conditional Breakpoints (Умовні Точки Зупинки)
Conditional breakpoints зупиняють виконання лише тоді, коли виконується певна умова. Це надзвичайно корисно для відлагодження циклів або рідкісних сценаріїв.
public class OrderProcessor
{
public void ProcessOrders(List<Order> orders)
{
foreach (var order in orders)
{
decimal total = CalculateTotal(order); // Умовний breakpoint: total > 1000
if (total > 0)
{
order.Status = "Processed";
}
}
}
private decimal CalculateTotal(Order order)
{
return order.Items.Sum(i => i.Price * i.Quantity);
}
}
Встановлення умовного breakpoint:
- Встановіть звичайний breakpoint на потрібному рядку
- Правий клік на червоному колі breakpoint
- Виберіть
Conditions...(Visual Studio) абоEdit Breakpoint...(VS Code) - Вкажіть умову, наприклад:
total > 1000абоorder.CustomerId == "VIP123" - Натисніть
EnterабоClose
// Зупинитися, коли сума перевищує 1000
total > 1000
// Зупинитися для конкретного користувача
string.IsNullOrEmpty(name)
// Зупинитися при комбінації умов
order.Status == "Pending" && order.Total > 500
Hit Count Breakpoints
Hit Count breakpoints зупиняють виконання після певної кількості проходжень через breakpoint.
Приклад: Зупинитися на 100-й ітерації циклу.
- Правий клік на breakpoint
Conditions...→ вкладкаHit Count- Виберіть умову (наприклад, "is equal to") та вкажіть число
100
Debugging Windows (Вікна Відлагодження)
Коли програма зупинена на breakpoint, Visual Studio надає кілька вікон для інспекції стану програми.
Locals Window (Вікно Локальних Змінних)
Locals Window автоматично відображає всі локальні змінні у поточному scope (області видимості), включаючи параметри методу.
public decimal CalculateDiscount(decimal price, int quantity)
{
decimal subtotal = price * quantity; // ← Breakpoint тут
decimal discount = 0;
if (subtotal > 100)
{
discount = subtotal * 0.1m; // 10% знижка
}
return subtotal - discount;
}
Що показує Locals Window на breakpoint (рядок 3):
| Ім'я | Значення | Тип |
|---|---|---|
price | 25.50 | decimal |
quantity | 5 | int |
subtotal | 127.50 | decimal |
discount | 0 | decimal |
Visual Studio: Debug → Windows → Locals або Ctrl+Alt+V, L

- Автоматичне оновлення: Значення оновлюються при кожному кроці
- Розгортання об'єктів: Клік на
▶розгортає properties та fields - Модифікація: Можна змінювати значення прямо у вікні (double-click на значенні)
- Контекстна навігація:
thisпоказує поточний екземпляр класу
Watch Window (Вікно Спостереження)
Watch Window дозволяє додавати власні вирази та змінні для постійного моніторингу, навіть якщо вони не входять у поточний scope.
Відкрити: Debug → Windows → Watch → Watch 1 або Ctrl+Alt+W, 1
public class ShoppingCart
{
private List<Item> items = new List<Item>();
public void AddItem(Item item)
{
items.Add(item); // ← Breakpoint тут
// Додайте у Watch:
// items.Count
// items.Sum(i => i.Price)
// item.Name
}
}
Вирази для Watch Window:
| Вираз | Опис |
|---|---|
items.Count | Кількість елементів у списку |
items.Sum(i => i.Price) | LINQ вираз для сумми цін |
item.Name.Length | Довжина назви товару |
DateTime.Now | Поточний час |

Call Stack Window (Вікно Стека Викликів)
Call Stack показує ланцюжок викликів методів, які привели до поточної точки виконання. Це критично важливо для розуміння контексту виконання.
class Program
{
static void Main(string[] args)
{
ProcessData(); // [1] Точка входу
}
static void ProcessData()
{
var result = CalculateSum(10, 20); // [2] Виклик CalculateSum
Console.WriteLine(result);
}
static int CalculateSum(int a, int b)
{
return Add(a, b); // [3] Виклик Add ← Breakpoint тут
}
static int Add(int x, int y)
{
return x + y; // [4] Поточне місце виконання
}
}
Call Stack на breakpoint (рядок 20):
Add(int x, int y) Line 20 ← Поточний frame
CalculateSum(int a, int b) Line 15
ProcessData() Line 10
Main(string[] args) Line 5
[External Code]
Відкрити: Debug → Windows → Call Stack або Ctrl+Alt+C
Використання Call Stack:
- Навігація: Double-click на будь-якому frame для переходу до цього місця в коді
- Контекст: Кожен frame зберігає значення локальних змінних (видно в Locals Window)
- Аналіз потоку: Розуміння, хто викликав поточний метод та з якими параметрами
Step-Through Debugging (Покрокове Виконання)
Після зупинки на breakpoint ви можете контролювати виконання коду по рядках.
Основні Команди Навігації
public class Calculator
{
public int Calculate(int a, int b)
{
int result = Add(a, b); // [1] Breakpoint тут
result = Multiply(result, 2); // [2]
return result; // [3]
}
private int Add(int x, int y)
{
return x + y; // [4]
}
private int Multiply(int x, int y)
{
return x * y; // [5]
}
}
| Команда | Клавіша | Опис | Поведінка на рядку 1 |
|---|---|---|---|
| Step Into | F11 | Заходить всередину викликів методів | Перехід всередину Add() → рядок 4 |
| Step Over | F10 | Виконує рядок повністю, не заходячи в методи | Виконує Add() цілком → рядок 2 |
| Step Out | Shift+F11 | Виконує до кінця поточного методу | Виконує весь Calculate() → повернення до caller |
| Continue | F5 | Продовжує виконання до наступного breakpoint | Виконується до наступного breakpoint або кінця |
Практичний Сценарій
Крок 1: Встановіть breakpoint на рядку 1
Крок 2: Запустіть відлагодження (F5)
Крок 3: Коли виконання зупиниться, використовуйте:
F11(Step Into) → заходимо в методAdd()- У методі
Add()натиснітьF10(Step Over) → виконуєтьсяreturn x + y Shift+F11(Step Out) → повертаємось до методуCalculate()на рядок 2

Візуалізація Flow Execution
F11) не працює з методами, для яких немає вихідного коду (наприклад, бібліотечні методи .NET). У такому разі він поводиться як Step Over.Run to Cursor (Виконати до Курсору)
Run to Cursor — це швидкий спосіб виконати код до певного рядка без встановлення breakpoint.
- Під час паузи на breakpoint поставте курсор на потрібний рядок
- Натисніть
Ctrl+F10або правий клік →Run to Cursor - Виконання продовжиться до цього рядка
Immediate Window (Вікно Негайного Виконання)
Immediate Window — це інтерактивна консоль, яка дозволяє виконувати C# вирази та команди під час паузи виконання.
Відкрити: Debug → Windows → Immediate або Ctrl+Alt+I
Оцінка Виразів
public void ProcessOrder(Order order)
{
string customerName = order.Customer.Name;
decimal total = order.CalculateTotal(); // ← Breakpoint тут
if (total > 100)
{
ApplyDiscount(order);
}
}
У Immediate Window (коли breakpoint активний):
// Перевірка значень
? customerName
"John Doe"
? total
125.50m
// Виклик методів
? order.Items.Count
3
// LINQ запити
? order.Items.Where(i => i.Price > 20).Count()
2
// Математичні операції
? total * 0.1
12.55m
// Умовні вирази
? total > 100
true
? є скороченням для команди Debug.Print(). Він виводить результат виразу у вікно.Виконання Коду "На Льоту"
Immediate Window дозволяє не тільки читати, але й змінювати стан програми:
// Змінити значення змінної
total = 50.0m
// Викликати метод
order.AddItem(new Item { Name = "Test", Price = 10 })
// Створити новий об'єкт
var testCustomer = new Customer { Name = "Test User" }
// Invoke приватні методи (через рефлексію)
? this.GetType().GetMethod("PrivateMethod",
BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(this, null)

Практичні Use Cases
Швидке тестування логіки
Перевірити вираз без зміни коду:
? DateTime.Now.AddDays(7).DayOfWeek
DayOfWeek.Monday
Інспекція приватних полів
Доступ до приватних членів через рефлексію або властивості:
? order.GetType().GetField("_internalId",
BindingFlags.NonPublic | BindingFlags.Instance).GetValue(order)
Симуляція сценаріїв
Модифікувати стан для тестування edge cases:
total = decimal.MaxValue
? order.CalculateTotal()
// Перевірка overflow handling
↑ та ↓ для навігації по попереднім командам.Практичний Debugging Workflow
Розглянемо повний цикл відлагодження реальної проблеми.
Сценарій: Некоректна Знижка
public class PriceCalculator
{
public decimal ApplyDiscount(decimal price, decimal discountPercent)
{
decimal discount = price * discountPercent;
return price - discount;
}
}
// Виклик:
var calculator = new PriceCalculator();
var finalPrice = calculator.ApplyDiscount(100, 20);
// Очікуємо: 80, Отримуємо: -1900 (?!)
Процес Відлагодження
1. Відтворення проблеми
- Запустіть код та підтвердіть некоректний результат
2. Встановлення breakpoint
- Breakpoint на рядку 5 (обчислення discount)
3. Запуск відлагодження
- Натисніть
F5для запуску
4. Інспекція у Locals Window
price = 100
discountPercent = 20
discount = 2000 ← Проблема!
5. Аналіз у Immediate Window
? price * discountPercent
2000m // Має бути 20!
? discountPercent / 100
0.20m // Ось правильне значення!
6. Виявлення помилки
discountPercentпередається як20, а не0.20- Формула обчислює
100 * 20 = 2000замість100 * 0.20 = 20
7. Виправлення
public decimal ApplyDiscount(decimal price, decimal discountPercent)
{
// Конвертуємо відсотки у десятковий формат
decimal discount = price * (discountPercent / 100);
return price - discount;
}
8. Верифікація
- Запустіть тести повторно
- Підтвердіть результат:
80
Debugging Checklist
null, 0, порожніх колекціях?Troubleshooting (Вирішення Типових Проблем)
Breakpoint не спрацьовує
Можливі причини та рішення:
Console.WriteLine() для перевірки.У Release mode компілятор може оптимізувати код. Перемкніться на Debug mode:
- Visual Studio:
Debug→ Configuration Manager → Active solution configuration →Debug
Переконаєтесь, що .pdb файли генеруються:
<PropertyGroup>
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
async методів переконайтесь, що breakpoint стоїть після await, а не на визначенні методу.Locals/Watch показує "Cannot evaluate expression"
| Причина | Рішення |
|---|---|
| Змінна оптимізована компілятором | Вимкніть оптимізацію: Проєкт → Properties → Build → Uncheck "Optimize code" |
| Змінна поза scope | Перевірте, що змінна досяжна у поточному контексті |
| Асинхронний контекст | Використовуйте Immediate Window з повним шляхом |
Immediate Window не працює
Надто повільне відлагодження
Оптимізації:
- Використовуйте Hit Count breakpoints замість складних умов
- Видаліть непотрібні Watch вирази
- Вимкніть "Enable property evaluation and other implicit function calls" в налаштуваннях Debug
Практичні Завдання
Закріпіть знання через практику!
Рівень 1: Основи Breakpoints
Завдання 1.1: Базовий Debugging
Створіть програму, яка обчислює суму чисел від 1 до 10:
int sum = 0;
for (int i = 1; i <= 10; i++)
{
sum += i;
}
Console.WriteLine($"Сума: {sum}");
Вимоги:
- Встановіть breakpoint на рядку 4
- Запустіть відлагодження
- У Locals Window знайдіть значення
sumтаiна кожній ітерації - Використовуйте
F10для покрокового виконання циклу - Запишіть значення
sumпісля 5-ї ітерації
Очікувана відповідь: sum = 15 після 5-ї ітерації
Завдання 1.2: Conditional Breakpoint
for (int i = 1; i <= 100; i++)
{
if (i % 7 == 0)
{
Console.WriteLine($"Знайдено: {i}");
}
}
Вимоги:
- Встановіть умовний breakpoint на рядку 5
- Умова:
i == 49 - Запустіть програму — вона має зупинитися тільки один раз
- Перевірте значення
iу Locals Window
Питання: Скільки разів виконається рядок 5 до спрацювання breakpoint? (Відповідь: 7 разів)
Рівень 2: Watch та Step-Through
Завдання 2.1: Використання Watch Window
public class Student
{
public string Name { get; set; }
public List<int> Grades { get; set; } = new List<int>();
public double GetAverage()
{
return Grades.Average(); // Breakpoint тут
}
}
var student = new Student { Name = "Іван" };
student.Grades.AddRange(new[] { 85, 90, 78, 92, 88 });
var avg = student.GetAverage();
Вимоги:
- Встановіть breakpoint на рядку 8
- Додайте у Watch Window:
Grades.CountGrades.Max()Grades.Min()Name.Length
- Запустіть відлагодження та запишіть всі значення
Очікувані результати:
- Count: 5
- Max: 92
- Min: 78
- Name.Length: 4
Завдання 2.2: Step Into vs Step Over
public int Calculate(int a, int b)
{
int sum = Add(a, b); // [1] Breakpoint
int product = Multiply(a, b); // [2]
return sum + product; // [3]
}
private int Add(int x, int y) => x + y;
private int Multiply(int x, int y) => x * y;
// Виклик:
Calculate(3, 4);
Експеримент:
- Встановіть breakpoint на рядку 1
- Сценарій A: Використайте
F11(Step Into) → куди ви потрапите? - Сценарій B: Поверніться, використайте
F10(Step Over) → що станеться?
Питання:
- У сценарії A ви потрапите всередину методу
Add()чи перейдете на рядок 2? (Відповідь: всерединуAdd()) - Запишіть значення
sumпісля виконання рядка 1: (Відповідь: 7)
Рівень 3: Складні Сценарії
Завдання 3.1: Відлагодження рекурсії
public int Factorial(int n)
{
if (n <= 1)
return 1; // [Base case]
return n * Factorial(n - 1); // [Recursive call] ← Breakpoint
}
// Виклик:
int result = Factorial(5);
Вимоги:
- Встановіть breakpoint на рядку 6
- Додайте
nу Watch Window - Використовуйте Call Stack Window для відстеження рекурсії
- Запишіть, скільки frames буде у Call Stack при
n = 1
Додаткове питання: Яке значення n у кожному frame Call Stack коли досягається base case?
Відповідь: Call Stack матиме 5 frames: Factorial(1) → Factorial(2) → Factorial(3) → Factorial(4) → Factorial(5)
Завдання 3.2: Debugging з Immediate Window
public class BankAccount
{
private decimal balance;
public void Deposit(decimal amount)
{
balance += amount; // Breakpoint тут
}
public bool Withdraw(decimal amount)
{
if (balance >= amount)
{
balance -= amount;
return true;
}
return false;
}
}
var account = new BankAccount();
account.Deposit(1000);
account.Withdraw(300);
account.Withdraw(500);
Челендж:
- Встановіть breakpoint на рядку 7
- Під час першого спрацювання у Immediate Window:
- Змініть
amountна5000 - Виконайте
F10(Step Over) - Перевірте
balance
- Змініть
Питання:
- Яке значення
balanceпісля модифікації? (Відповідь: 5000) - Спробуйте викликати
Withdraw(200)з Immediate Window під час паузи - Що покаже
balanceпісля цього виклику? (Відповідь: 4800)
Мета: Навчитися модифікувати стан програми для тестування edge cases
Завдання 3.3: Знайдіть Bug
public List<int> GetEvenNumbers(List<int> numbers)
{
var result = new List<int>();
for (int i = 0; i < numbers.Count; i++)
{
if (numbers[i] % 2 == 0)
{
result.Add(numbers[i]);
}
i++; // ← Є тут проблема?
}
return result;
}
// Тест:
var input = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
var evens = GetEvenNumbers(input);
// Очікуємо: [2, 4, 6, 8]
// Отримуємо: [2, 6] (половина пропущена!)
Завдання:
- Знайдіть помилку використовуючи breakpoint та Watch Window
- Додайте у Watch:
i,numbers[i],result.Count - Використовуйте Step Over (
F10) для проходу циклу - Визначте, чому пропускаються елементи
Підказка: Зверніть увагу на рядок 11
Виправлення: Видаліть рядок 11 (i++) — інкремент вже виконується у for циклі!
Резюме
У цьому розділі ви освоїли фундаментальні техніки відлагодження C# коду:
Breakpoints
- Встановлення, вимкнення та видалення точок зупинки
- Умовні breakpoints для складних сценаріїв
- Hit count breakpoints для циклів
Debugging Windows
- Locals: автоматична інспекція локальних змінних
- Watch: моніторинг користувацьких виразів
- Call Stack: аналіз потоку виконання
Step-Through
- Step Into (
F11): заглиблення в методи - Step Over (
F10): виконання рядка цілком - Step Out (
Shift+F11): вихід з методу
Immediate Window
- Оцінка виразів у реальному часі
- Виконання коду "на льоту"
- Модифікація стану для тестування
Наступні Кроки
Тепер, коли ви володієте базовими техніками відлагодження, ви готові:
- Працювати з виключеннями: Налаштування Exception Settings для перехоплення помилок
- Профілювати код: Використання Performance Profiler для оптимізації
- Відлагоджувати асинхронний код: Особливості роботи з
async/await - Використовувати IntelliTrace: Історичне відлагодження (Visual Studio Enterprise)
Продовжуйте практикуватися, і debugging стане вашою суперсилою! 🚀