Изключенията в .NET са класическа имплементация на изключенията от ООП, макар че притежават и допълнителни възможности, произтичащи най-вече от предимствата на управлявания код.
В .NET Framework управлението на грешките се осъществява предимно чрез изключения. Всички операции от стандартната библиотека на .NET (Framework Class Library) сигнализират за грешки посредством хвърляне (throw, raise) на изключение. .NET програмистите трябва да се съобразяват с изключенията, които биха могли да възникнат и да предвидят код за тяхната обработка в някой от извикващите методи.
Изключение може да възникне поради грешка в нашия код или в код който извикваме (примерно библиотечни функции), при изчерпване на ресурс на операционната система, при неочаквано поведение в .NET средата (примерно невъзможност за верификация на даден код) и в много други ситуации.
В повечето случаи едно приложение е възможно да се върне към нормалната си работа след обработка на възникнало изключение, но има и ситуации в които това е невъзможно. Такъв е случаят при възникване на някои runtime изключения. Пример за подобна изключителна ситуация е, когато една програма изчерпа наличната работна памет. Тогава CLR хвърля изключение, което сигнализира за настъпилия проблем, но програмата не може да продължи нормалната си работа и единствено може да запише състоянието на данните, с които работи (за да минимизира загубите), и след това да прекрати изпълнението си.
Всички изключения в .NET Framework са обекти, наследници на класа System.Exception, който ще разгледаме в детайли след малко. Всъщност, съществуват и изключения, които не отговарят на това изискване, но те са нестандартни и възникват рядко. Тези изключения не са съвместими със CLS (Common Language Specification) и не могат да се предизвикат от .NET езиците (C#, VB.NET и т. н.), но могат да възникнат при изпълнение на неуправляван код.
Изключенията носят в себе си информация за настъпилите грешки или необичайни ситуации. Тази информация може да се извлича от тях и е много полезна за идентифицирането на настъпилия проблем. В .NET Framework изключенията пазят в себе си името на класа и метода, в който е възникнал проблемът, а ако асемблито е компилирано с дебъг информация, изключенията пазят и името на файла и номера на реда от сорс кода, където е възникнал проблемът.
Когато възникне изключение, изпълнението на програмата спира. CLR средата запазва състоянието на стека и търси блока от кода, отговорен за прихващане и обработка на възникналото изключение. Ако не го намери в границите на текущия метод, го търси в извикващия го метод. Ако и в него не го намери, го търси в неговия извикващ и т. н. Ако никой от извикващите методи не прихване изключението, то се прихваща от CLR, който показва на потребителя информация за възникналия проблем.
Изключенията улесняват писането и поддръжката на надежден програмен код, като дават възможност за обработката на проблемните ситуации на много нива. В .NET Framework се позволява хвърляне и прихващане на изключения дори извън границите на текущия процес.
Прихващане на изключения
Работата с изключения включва две основни операции – прихващане на изключения и предизвикване (хвърляне) на изключения. Нека разгледаме първо прихващането на изключения в езика C#.
Програмна конструкция try-catch
В C# изключенията се прихващат с програмната конструкция try-catch:
Кодът, който може да предизвика изключение, се поставя в try блока, а кодът, отговорен за обработка му – в catch блока.
Catch блокът може да посочи т. нар. филтър за прихващане на изключения или да го пропусне. Филтърът представлява име на клас, поставен в скобки като параметър на catch оператора. В горния пример филтърът задава прихващане на изключения от класа SomeExceptionClass и всички класове, негови наследници. Ако филтърът бъде пропуснат, се прихващат всички изключения, независимо от типа им:
Изразът catch може да присъства няколко пъти съответно за различните типове изключения, които трябва да бъдат прихванати, например:
try
{
// Do some work that can raise an exception
}
catch (SomeExceptionClass)
{
// Handle the SomeExceptionClass and its descendants
}
catch (OtherExceptionClass)
{
// Handle the OtherExceptionClass and its descendants
}
| Как CLR търси обработчик за изключенията?
Когато възникне изключение, CLR търси "най-близкия" catch блок, който може да обработи типа на възникналото изключение. Първо се претърсва try-catch блокът от текущия метод, към който принадлежи изпълняваният в момента код (ако има такъв блок). Последователно се обхождат асоциираните с него catch блокове, докато се намери този, чийто филтър съответства на типа на възникналото изключение.
Ако това претърсване пропадне, се извършва същото претърсване за следващия try-catch блок, ограждащ текущия (ако има такъв). Този блок може да се намира в текущия метод, в извикващия го метод или в някой от методите, които са извикали него. Ако търсенето отново пропадне, се търси следващия try-catch блок и се проверяват неговите филтри дали улавят възникналото изключение. Търсенето продължава докато се намери първият подходящ обработчик на възникналото изключение или се установи, че няма изобщо такъв.
Търсенето може да обходи целия стек на извикване на методите и да не успее да намери catch блок, който да обработи изключението. В такъв случай изключението се обработва от CLR (появява се съобщение за грешка).
Прихващане на изключения – пример
Нека разгледаме един прост пример:
static void Main()
{
string s = Console.ReadLine();
try
{
Int32.Parse(s);
Console.WriteLine("You entered valid Int32 number {0}", s);
}
catch (FormatException)
{
Console.WriteLine("Invalid integer number!");
}
catch (OverflowException)
{
Console.WriteLine("Number too big to fit in Int32!");
}
}
|
В този пример програма очаква да се въведе цяло число. Ако потребителят въведе нещо различно, ще възникне изключение.
Извикването на метода Int32.Parse(s) може да предизвика различни изключения и затова е поставено в try блок, към който са асоциирани няколко catch блока.
Ако вместо число се подаде някаква произволна комбинация от символи, при извикването на метода Int32.Parse(s) ще възникне изключението System.FormatException, което ще бъде прихванато и обработено от първия catch блок.
Ако потребителят въведе число, по-голямо от максималната стойност за типа System.Int32, при извикването на Int32.Parse(s) ще възникне System.OverflowException, чиято обработка се извършва от втория catch блок.
Всеки catch блок е подобен на метод който приема точно един аргумент от определен тип изключение. Този аргумент може да бъде зададен само с типа на изключението, както е в по-горния пример, а може да се зададе и променлива:
catch (OverflowException ex)
{
// Handle the caught exception
}
|
Тук посредством от променливата еx, която е инстанция на класа System.OverflowException, можем да извлечем допълнителна информация за възникналото изключение.
Прихващане на изключения на нива – пример
Нека сега разгледаме един по-сложен пример за прихващане на изключения – прихващане на изключения на няколко нива:
static void Main()
{
try
{
int result = Calc(100000, 100000, 1);
Console.WriteLine(result);
}
catch (ArithmeticException)
{
Console.WriteLine("Calculation failed!");
}
}
static int Calc(int a, int b, int c)
{
int result;
try
{
checked
{
result = a*b/c;
}
}
catch (DivideByZeroException)
{
result = -1;
}
return result;
}
|
В този пример изключенията се прихващат на 2 нива – в try-catch блок в метода Calc(…) и в try-catch блок в метода Main(), извикващ Calc(…).
Ако методът Calc(…) бъде извикан с параметри (0, 0, 0), ще се получи деление на 0 и изключението DivideByZeroException ще бъде прихванато и обработено в try-catch блока на Calc(…) метода и съответно ще се получи стойност -1.
Ако, обаче, методът Calc(…) бъде извикан с параметри (100000, 100000, 1), ще се получи препълване на типа int, което в checked блок ще предизвика ArithmeticOverflowException. Това изключение няма да бъде хванато от catch филтъра в Calc(…) метода и CLR ще провери следващия catch филтър. Това е try-catch блокът в метода Main(), от който е извикан методът Calc(…). CLR ще открие в него е подходящ обработчик за изключението (catch филтърът за класа ArithmeticException, на който класът ArithmeticOverflowException е наследник) и ще го изпълни. Резултатът ще е отпечатване на съобщението "Calculcation failed!".
Възможно е по някаква причина в Calc(…) метода да възникне изключение, което не е наследник на ArithmeticException (например OutOfMemoryException). В такъв случай то няма да бъде прихванато от никой от catch филтрите и ще се обработи от CLR.
Сподели с приятели: |