Уявіть собі ситуацію: ваша програма компілюється без помилок, але при виконанні видає неочікуваний результат. Користувачі скаржаться, що їхні замовлення обробляються з неправильними цінами. Де саме виникає проблема? Яка змінна містить некоректне значення? У якій послідовності викликаються методи?
Саме для вирішення таких задач існує відлагодження (debugging) — процес виявлення та усунення помилок у коді. Відлагоджувач (debugger) — це потужний інструмент, який дозволяє зупиняти виконання програми, інспектувати стан змінних, покроково виконувати код та аналізувати потік виконання.
Після вивчення цього розділу ви зможете:
Для роботи з цим розділом вам необхідно:
Відлагодження (Debugging) — це систематичний процес виявлення, локалізації та усунення помилок (bugs) у програмному коді. Це не просто "виправлення того, що не працює", а наукова методика аналізу поведінки програми на різних рівнях абстракції.
Visual Studio надає один з найпотужніших відлагоджувачів для .NET розробки. Він інтегрований безпосередньо в IDE та підтримує:
Breakpoint — це маркер у коді, який інструктує відлагоджувач призупинити виконання програми у вказаному місці. Це найбазовіший та найважливіший інструмент відлагодження.
Три способи встановити breakpoint:
F9Debug → Toggle BreakpointBreakpoint візуально відображається як червоне коло на полі редактора.

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

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 зупиняють виконання лише тоді, коли виконується певна умова. Це надзвичайно корисно для відлагодження циклів або рідкісних сценаріїв.
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:
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 зупиняють виконання після певної кількості проходжень через breakpoint.
Приклад: Зупинитися на 100-й ітерації циклу.
Conditions... → вкладка Hit Count100Коли програма зупинена на breakpoint, Visual Studio надає кілька вікон для інспекції стану програми.
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 та fieldsthis показує поточний екземпляр класу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 показує ланцюжок викликів методів, які привели до поточної точки виконання. Це критично важливо для розуміння контексту виконання.
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:
Після зупинки на 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 + yShift+F11 (Step Out) → повертаємось до методу Calculate() на рядок 2
F11) не працює з методами, для яких немає вихідного коду (наприклад, бібліотечні методи .NET). У такому разі він поводиться як Step Over.Run to Cursor — це швидкий спосіб виконати код до певного рядка без встановлення breakpoint.
Ctrl+F10 або правий клік → Run to CursorImmediate 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)

Швидке тестування логіки
Перевірити вираз без зміни коду:
? 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
↑ та ↓ для навігації по попереднім командам.Розглянемо повний цикл відлагодження реальної проблеми.
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
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.20100 * 20 = 2000 замість 100 * 0.20 = 207. Виправлення
public decimal ApplyDiscount(decimal price, decimal discountPercent)
{
// Конвертуємо відсотки у десятковий формат
decimal discount = price * (discountPercent / 100);
return price - discount;
}
8. Верифікація
80null, 0, порожніх колекціях?Можливі причини та рішення:
Console.WriteLine() для перевірки.У Release mode компілятор може оптимізувати код. Перемкніться на Debug mode:
Debug → Configuration Manager → Active solution configuration → DebugПереконаєтесь, що .pdb файли генеруються:
<PropertyGroup>
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
async методів переконайтесь, що breakpoint стоїть після await, а не на визначенні методу.| Причина | Рішення |
|---|---|
| Змінна оптимізована компілятором | Вимкніть оптимізацію: Проєкт → Properties → Build → Uncheck "Optimize code" |
| Змінна поза scope | Перевірте, що змінна досяжна у поточному контексті |
| Асинхронний контекст | Використовуйте Immediate Window з повним шляхом |
Оптимізації:
Закріпіть знання через практику!
Завдання 1.1: Базовий Debugging
Створіть програму, яка обчислює суму чисел від 1 до 10:
int sum = 0;
for (int i = 1; i <= 10; i++)
{
sum += i;
}
Console.WriteLine($"Сума: {sum}");
Вимоги:
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}");
}
}
Вимоги:
i == 49i у Locals WindowПитання: Скільки разів виконається рядок 5 до спрацювання breakpoint? (Відповідь: 7 разів)
Завдання 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();
Вимоги:
Grades.CountGrades.Max()Grades.Min()Name.LengthОчікувані результати:
Завдання 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);
Експеримент:
F11 (Step Into) → куди ви потрапите?F10 (Step Over) → що станеться?Питання:
Add() чи перейдете на рядок 2? (Відповідь: всередину Add())sum після виконання рядка 1: (Відповідь: 7)Завдання 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);
Вимоги:
n у Watch Windown = 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);
Челендж:
amount на 5000F10 (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] (половина пропущена!)
Завдання:
i, numbers[i], result.CountF10) для проходу циклуПідказка: Зверніть увагу на рядок 11
Виправлення: Видаліть рядок 11 (i++) — інкремент вже виконується у for циклі!
У цьому розділі ви освоїли фундаментальні техніки відлагодження C# коду:
Breakpoints
Debugging Windows
Step-Through
F11): заглиблення в методиF10): виконання рядка цілкомShift+F11): вихід з методуImmediate Window
Тепер, коли ви володієте базовими техніками відлагодження, ви готові:
async/awaitПродовжуйте практикуватися, і debugging стане вашою суперсилою! 🚀