Когато нещо се обърка
Проблем
12а вместо 12
Касата за концерта пита колко билета искаш. Билетът е 15 €.
class Program{ static void Main() { Console.Write("Колко билета? "); int tickets = int.Parse(Console.ReadLine()); Console.WriteLine("Общо: " + tickets * 15 + " €"); }}С вход 12 програмата казва „Общо: 180 €“. Но клиентът бърза и натиска една буква повече:
Колко билета? 12аUnhandled exception. System.FormatException: The input string '12а' was not in a correct format. at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value) at System.Int32.Parse(String s) at Program.Main() in /projects/Tickets/Program.cs:line 6Програмата спира. Вместо сумата излиза дълъг текст, а редът „Общо“ изобщо не се стига. Какво значи този текст и как да не спира програмата заради една буква?
Идеята
Изключение
int.Parse трябва да върне число. От „12а“ не може да направи число, а да върне каквото и да е число би било лъжа. Затова методът създава обект, който описва проблема, и го хвърля. Този обект се казва изключение (exception).
Ако никой не го хване, програмата спира и показва какво е станало. Този текст се казва stack trace и се чете отгоре надолу:
- Първият ред: видът на изключението и съобщението.
FormatException: The input string '12а' was not in a correct format.: грешен формат на числото, ето кой текст. - Редовете с „at“: къде е станало. Това е стекът от извиквания от урока за рекурсията, отвътре навън. Най-горе е методът, който е хвърлил изключението, а най-долу е
Main. - Твоят ред: първият ред „at“, който сочи твоя файл. Тук:
Program.cs:line 6. Редовете над него са в кода на .NET и рядко ти трябват.
Съобщенията са на английски, защото ги пише езикът, не ти.
Пробвай
Намери грешката
Програмата трябва да отпечата последния резултат от контролното. Тя спира с това:
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array. at Program.LastScore(Int32[] scores) in /projects/Tickets/Program.cs:line 5 at Program.Main() in /projects/Tickets/Program.cs:line 11Прочети stack trace-а. На кой ред от програмата е грешката?
Избери реда с грешката.
LastScore, ред 5. Редът под него е Main, ред 11: оттам е извикан методът, но грешката не е там. Първият ред казва и каква е тя: индекс 5 при дължина 5. Последният индекс е scores.Length - 1.Идеята
try и catch
Изключението може да се хване. Кодът, който може да хвърли, отива в try („опитай“). Какво да стане при грешка, пишеш в catch („хвани“):
Console.Write("Колко билета? ");try{ int tickets = int.Parse(Console.ReadLine()); Console.WriteLine("Общо: " + tickets * 15 + " €");}catch (FormatException){ Console.WriteLine("Това не е цяло число.");}Console.WriteLine("Благодарим ти!");12аКолко билета? Това не е цяло число.Благодарим ти!- В скобите на
catchпишеш кой вид изключение хващаш. - Ако в
tryвсичко мине,catchсе прескача. С вход12изходът е „Общо: 180 €“ и „Благодарим ти!“. - След
catchпрограмата продължава нататък. Тя вече не спира.
Console.Write("Колко билета? ");try{ int tickets = int.Parse(Console.ReadLine()); Console.WriteLine("Общо: " + tickets * 15 + " €");}catch (FormatException){ Console.WriteLine("Това не е цяло число.");}Console.WriteLine("Благодарим ти!");Стъпка по стъпка, с думи
- Програмата пита колко билета. Въвеждаш „12а“.
- В try: int.Parse опитва да направи число от „12а“.
- Не може. Методът хвърля FormatException.
- Изпълнението скача направо в catch. Редът с „Общо“ се прескача.
- catch е за точно този вид грешка: печата съобщението за клиента.
- След catch програмата продължава. Не спира.
По-точно, за любопитните
- Променлива, обявена в
try(тукtickets), съществува само до затварящата скоба наtry. Ако ти трябва и след блока, обяви я предиtry.
- В C# можеш да не дадеш име на изключението:
catch (FormatException). Така е, когато не ти трябва. Ако напишеш име и не го ползваш, компилаторът дава предупреждение.
Пробвай
Кое се изпълнява?
Какво ще отпечата програмата?
Console.WriteLine("Начало");try{ Console.WriteLine("Чета"); int seats = int.Parse("десет"); Console.WriteLine("Прочетох " + seats);}catch (FormatException){ Console.WriteLine("Грешен вход");}Console.WriteLine("Край");„Чета“ се отпечатва, защото е преди грешката. После Parse хвърля и изпълнението скача направо в catch. „Прочетох“ се пропуска. След catch програмата продължава с „Край“.
Идеята
Какво пише в изключението
Изключението е обект, а e в catch (FormatException e) е променливата, която сочи към него. Обектът има метод за съобщението: e.Message.
try{ int tickets = int.Parse(Console.ReadLine()); Console.WriteLine("Общо: " + tickets * 15 + " €");}catch (FormatException e){ Console.WriteLine("Грешка: " + e.Message);}12аГрешка: The input string '12а' was not in a correct format.Съобщението е полезно за теб, когато търсиш грешката. На клиента обикновено показваш свой текст на неговия език, като „Това не е цяло число.“
Идеята
Питай, докато е вярно
Клиентът сбърка. Не е нужно да спираш. Питай отново:
int tickets;while (true){ Console.Write("Колко билета? "); try { tickets = int.Parse(Console.ReadLine()); break; } catch (FormatException) { Console.WriteLine("Въведи цяло число, например 3."); }}Console.WriteLine("Общо: " + tickets * 15 + " €");12адванайсет12Колко билета? Въведи цяло число, например 3.Колко билета? Въведи цяло число, например 3.Колко билета? Общо: 180 €while (true) се върти, докато break не го прекъсне. А break се стига само ако Parse е успял. При грешка изпълнението скача в catch, след него цикълът започва отначало и пита пак.
По-точно, за любопитните
- Когато грешният вход е нещо нормално, в C# има по-удобен начин:
int.TryParse. Той не хвърля изключение, а връщаtrueилиfalse, и записва числото вoutпараметъра от урока за параметрите:
int tickets;Console.Write("Колко билета? ");while (!int.TryParse(Console.ReadLine(), out tickets)){ Console.WriteLine("Въведи цяло число, например 3."); Console.Write("Колко билета? ");}Console.WriteLine("Общо: " + tickets * 15 + " €");Изключенията са за ситуации, които не очакваш често. За проверка на вход TryParse е по-кратък и по-бърз.
Пробвай
Деление на нула
Не всяко изключение идва от входа. Средната стойност на поръчка е приходът, делен на броя поръчки. Но в първия ден поръчки няма. С цели числа revenue / orders спира програмата:
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.А с дробни числа?
Какво ще отпечата програмата?
int revenue = 90;int orders = 0;double average = (double)revenue / orders;Console.WriteLine(average);NaN („не е число“). Програмата продължава с грешна стойност, без да каже нищо. Затова преди такова деление провери делителя с if.По-точно, за любопитните
- Тук изходът е
Infinity. На компютър с регионални настройки, например български или американски, C# печата знака∞.
- Целите числа нямат стойност за безкрайност, затова там остава само изключението. При дробните числа правилата идват от стандарта за дробни числа IEEE 754, който всички езици следват.
Упражнение
Програми, които не спират
Реши задачите в редактора си. Не печатай въпроси, за да съвпадне изходът ти с примерния.
1. Текст към число. Напиши метод ToNumber(string text), който връща текста като double. Прочети два реда. За всеки отпечатай числото или съобщение, че не е число. Хвани изключението в Main.
123.45123.abcЧислото е: 123.45Грешка: 123.abc не е число.На компютър с български настройки double.Parse иска запетая: 123,45. Виж урока „Вход и изход“.
2. Няма такъв продукт. Цените са в масив { 120, 75, 300, 45, 89 }. Прочети номер на продукт от 1 до 5 и отпечатай цената му. Ако номерът е извън масива, хвани изключението и го кажи. С вход 2 изходът е Цена: 75 €.
7Няма продукт с номер 7.3. Средна поръчка. Прочети прихода и броя поръчки (цели числа) и отпечатай средното с целочислено деление. Ако поръчки няма, хвани изключението. С вход 900 и 4 изходът е Средно: 225 €.
900Още няма поръчки.Още задачи
- Възраст, докато е вярна. Питай за възраст, докато входът не е цяло число от 0 до 120. За текст като „двайсет“ кажи „Това не е число.“, а за 150 кажи „Възрастта е от 0 до 120.“ Едното е изключение, другото е
if. - Сума на верните. Прочети 5 реда. Събери тези, които са цели числа, и преброй пропуснатите: при вход
10,abc,5,7.5,3изходът еСума: 18, пропуснати: 2. - Деление по желание. Прочети две цели числа и отпечатай частното. При текст вместо число и при деление на нула програмата не спира, а пита отново за двете числа.
- Без изключение. Реши задача 1 с
double.TryParseвместо сtry/catch.
Тест