OOP Course
Сьогодні

Підрозділ 2.10

Явні та неявні перетворення

Пояснює явні та неявні перетворення, механіку доповнення бітів, checked-контекст і поведінку при переповненні.

2.10. Явні та неявні перетворення

У попередньому розділі ми з'ясували, що перетворення типів буває розширювальним і звужувальним. Але є ще одна важлива класифікація: явні (explicit) та неявні (implicit) перетворення. Ця класифікація описує не напрямок перетворення (ширше чи вужче), а те, хто відповідає за його виконання — компілятор автоматично чи розробник вручну.

Явні та неявні перетворенняЯвні та неявні перетворення

Неявні перетворення

Неявне перетворення (implicit conversion) — це таке перетворення, яке компілятор виконує автоматично, без жодних явних вказівок у коді. Розробнику достатньо просто записати значення у змінну іншого типу, і все відбувається само собою.

Неявні перетворення можливі лише в тих випадках, коли вони гарантовано безпечні щодо діапазону — тобто коли жодне значення вихідного типу не може вийти за межі цільового типу. Саме тому компілятор без вагань виконує, наприклад, byte → int: будь-яке значення byte (0–255) без проблем вписується в int (–2 млрд до +2 млрд).

Розширення зі знаком (sign extension)

Коли неявно розширюється беззнаковий тип (наприклад, byte), ліворуч додаються нулі. Це називається доповненням нулями. Але коли розширюється знаковий тип (sbyte, short, int), механізм інший: ліворуч додається копія знакового розряду (старшого біту). Для позитивних чисел знаковий розряд дорівнює 0 — додаються нулі. Для від'ємних він дорівнює 1 — додаються одиниці. Завдяки цьому від'ємне число після розширення залишається від'ємним і зберігає своє значення:

Розглянемо розширення позитивного числа. Знаковий біт 0 — ліворуч додаються нулі:

Sign extension позитивного числа: sbyte 4 → shortSign extension позитивного числа: sbyte 4 → short

Розширення від'ємного числа: знаковий біт 1 — ліворуч додаються одиниці, завдяки чому від'ємне значення зберігається:

Sign extension від'ємного числа: sbyte -4 → shortSign extension від'ємного числа: sbyte -4 → short

Без цього механізму розширення знакових від'ємних чисел давало б абсолютно невірний результат — замість -4 отримали б якесь величезне позитивне число.

Явні перетворення

Явне перетворення (explicit conversion) — це перетворення, яке компілятор не може виконати автоматично без явної вказівки розробника. Причина в тому, що такі перетворення потенційно небезпечні: не для всіх значень вихідного типу гарантується коректний результат у цільовому типі. Розробник повинен сам сказати: «Я розумію ризик і беру відповідальність».

Синтаксис явного приведення:

(цільовий_тип) вираз

Безпечні ланцюжки неявних перетворень

Компілятор дозволяє неявні перетворення, де значення гарантовано вміщується в цільовий тип. Основні ланцюжки:

byte  → short → int → long → decimal
int   → double
short → float  → double
char  → int    → long

Це спрощена картина. Повний перелік неявних числових перетворень за специфікацією C#:

Звідки Куди (неявно)
sbyte short, int, long, float, double, decimal
byte short, ushort, int, uint, long, ulong, float, double, decimal
short int, long, float, double, decimal
ushort int, uint, long, ulong, float, double, decimal
int long, float, double, decimal
uint long, ulong, float, double, decimal
long float, double, decimal
ulong float, double, decimal
char ushort, int, uint, long, ulong, float, double, decimal
float double

Гарантія стосується діапазону, а не точності. Перетворення цілих типів у float (з int, uint, long, ulong) і в double (з long, ulong) можуть мовчки втратити молодші цифри: у float лише 24 біти мантиси, у double — 53.

Зверніть увагу також на відсутні перетворення: зі знакових типів у беззнакові (int → uint, sbyte → byte) неявного шляху немає — від'ємне значення не вміщується в беззнаковий тип.

Таблиця безпечних неявних перетвореньТаблиця безпечних неявних перетворень

Усе, що виходить за межі цих ланцюжків, вимагає явного (тип). Зокрема, хоча decimal технічно «більший» за double (28–29 значущих цифр проти 15–16), пряме неявне перетворення між ними не підтримується ні в якому напрямку. Ці типи принципово різні за структурою: double — це IEEE 754 бінарне число з плаваючою точкою, а decimal — десяткове число з фіксованою точністю. Пряме перетворення між ними може призвести до незначних похибок, тому компілятор змушує розробника зробити це явно:

Втрата даних при явному перетворенні

Явне перетворення до типу меншої розрядності нерідко призводить до втрати даних. Якщо значення не вписується в цільовий тип — старші біти просто відкидаються, і результат виявляється невірним:

Чому 121? Число 633 у двійковому вигляді — 10 01111001. Тип byte зберігає лише молодші 8 біт: 01111001 — це 121. Старший біт 1 відкинутий. Програма не повідомить про помилку — вона просто продовжить роботу з неправильним значенням. Саме це й є найнебезпечнішим у переповненні: помилка залишається прихованою.

Ключове слово `checked`

Щоб захиститись від мовчазного переповнення, C# надає ключове слово checked. Воно змушує середовище виконання перевіряти, чи не виходить результат арифметичної операції або приведення за межі цільового типу. Якщо виходить — кидається виняток OverflowException.

Без checked — програма мовчки дасть 121. З checked — виняток зупинить виконання в місці переповнення, і ми одразу побачимо проблему. Це особливо важливо в математично критичних місцях — розрахунках доз ліків, фінансових транзакціях або алгоритмах, де точність є необхідністю.

Існує також протилежне ключове слово unchecked — воно явно вказує, що переповнення дозволене і не має кидати виняток. Для виразів, що обчислюються під час виконання, це стандартна поведінка за замовчуванням (якщо в проєкті не ввімкнено <CheckForOverflowUnderflow>). Константні вирази компілятор перевіряє завжди: (byte)633 чи (byte)(33 + 600) з літералами дають помилку компіляції CS0221, а int.MaxValue + 1 — CS0220; саме тому в прикладах вище значення зберігаються у змінних a і b (явний unchecked((byte)633) цю перевірку знімає і дає 121). Крім того, unchecked може знадобитися всередині checked-блоку, якщо для якоїсь конкретної операції переповнення є навмисним:

checked і unchecked можна застосовувати як до окремого виразу (через дужки), так і до цілого блоку коду:

checked
{
    // Усі арифметичні операції в цьому блоці перевіряються
    int x = int.MaxValue;
    int y = x + 1;  // OverflowException
}

На практиці checked доцільно вмикати для критичних обчислень, де переповнення неприпустиме і повинно бути негайно виявлене, а не призводити до прихованих помилок у даних.

Комплексний приклад: зважений розрахунок дози

Розроблено Tomka Yurii · © 2026 ·