Кратко съдържание


Предизвикване (хвърляне) на изключения



страница37/73
Дата21.07.2018
Размер9.03 Mb.
#76887
1   ...   33   34   35   36   37   38   39   40   ...   73

Предизвикване (хвърляне) на изключения


Досега разгледахме как се прихващат изключения, които са предизвикани от някой друг. Нека сега разгледаме как ние можем да предизвикваме изключения, които някой друг да прихваща.

Предизвикването (хвърляне) на изключения (throwing, raising exceptions) има за цел да уведоми извикващия код за възникването на даден проблем. Тази техника се използва при настъпване на грешка или необичайна ситуация в даден програмен фрагмент. Под "необичайна ситуация" се има предвид ситуация, която разработчикът е предвидил като евентуално възможна, но която не се случва при нормалната работа, примерно опит за намиране на корен квадратен от отрицателно число.

При една такава необичайна ситуация изпълнението на програмата е нормално да продължи, а извикващият текущия метод трябва да бъде информиран за проблема, за да може да реагира по подходящ начин.

За да се хвърли изключение, с което да се уведоми извикващият код за даден проблем в C# се използва оператора throw, на който се подава инстанция на класа на изключението. Най-често се изисква създа­ване на обект от някой наследник на класа System.Exception, в който се поставя описа­ние на възникналия проблем. Ето един пример, в който се хвърля изключение ArgumentException:

throw new ArgumentException("Invalid argument!");

Обикновено преди да бъде хвърлено изключение, то се създава чрез извикване на конструктора на класа, на който то принадлежи. Почти всички изключения дефинират следните два конструктора:

Exception(string message);

Exception(string message, Exception InnerException);



Първият конструктор приема текстово съобщение, което описва възник­налият проблем, а вторият приема и изключение, причинител на възник­налия проблем.

При хвърляне на изключение CLR прекратява изпълнението на програмата и обхожда стека до достигане на catch блок за съответното изключение (целият процес беше описан подробно преди малко).


Хвърляне и прихващане на изключения – пример


Ето един пример за хвърляне и прихващане на изключение:

public static double Sqrt(double aValue)

{

if (aValue < 0)



{

throw new System.ArgumentOutOfRangeException(

"Sqrt for negative numbers is undefined!");

}

return Math.Sqrt(aValue);



}
static void Main()

{

try



{

Sqrt(-1);

}

catch (ArgumentOutOfRangeException ex)



{

Console.Error.WriteLine("Error: " + ex.Message);

}

}


В него е дефиниран метод, който извлича корен квадратен от реално число с двойна точност. При подаване на отрицателен аргумент методът хвърля ArgumentException. В Main() метода изключението се прихваща и се отпечатва грешка.

Хвърляне на прихванато изключение – пример


В catch блокове прихванатите изключения могат да се хвърлят отново. Пример за такова поведение е следният програмен фрагмент:

public static int Calculate(int a, int b)

{

try



{

return a/b;

}

catch (DivideByZeroException)



{

Console.WriteLine("Calculation failed!");

throw;

}

}


static void Main()

{

try



{

Calculate(1, 0);

}

catch (Exception ex)



{

Console.WriteLine(ex);

}

}


В метода Calculate(…) прихванатото аритметично изключение се обра­ботва като се отпечатва на конзолата "Calculation failed!" и след това се хвърля отново (чрез израза throw;). В резултат същото изключение се прихваща и от try-catch блока в Main() метода.

Собствени изключения


В .NET Framework програмистите могат да дефинират собствени класове за изключения и да създават класови йерархии с тях. Това осигурява много голяма гъвкавост при управлението на грешки и необичайни ситу­ации. В по-големите приложения изключенията се разделят в логически в категории и за всяка категория се дефинира по един базов клас, а за конкретните представители на категориите се дефинира по един клас-наследник. Ето един пример:

В примера се създава по един абстрактен базов клас за категорията изключения, свързани с клиентите (CustomerException) и за категорията изключения, свързани с поръчките (OrderException). Наследниците на OrderException и CustomerException също могат да се подреждат в класова йерархия и да дефинират собствени подкатегории.

При работата на приложението, използващо класовата йерархия от примера могат да се прихващат наведнъж всички грешки, свързани с клиентите или само някои конкретни от тях. Това дава добра гъвкавост при управлението на грешките.

Добре е да се спазва правилото, че йерархиите трябва да са широки и плитки, т.е. класовете на изключения трябва да са производни на тип, който се намира близо до System.Exception, и трябва да бъдат не повече от две или три нива надълбоко. Ако дефинираме тип за изключение, който няма да бъде базов за други типове, маркираме го като sealed, а ако не искаме да бъде инстанциран директно, го правим абстрактен.


Дефиниране на собствени изключения


За дефинирането на собствени изключения се наследява класът System. ApplicationException и му се създават подходящи конструктори и евен­туално му се добавят и допълнителни свойства, даващи специфична информация за проблема. Препоръчва се винаги да се дефинират поне следните два конструк­тора:

MyException(string message);

MyException(string message, Exception InnerException);



Въпреки, че не е задължително, силно се препоръчва имената на изключенията да завършват на "Exception", например OrderException, CustomerNotFoundException, InvalidCredentialsException и т. н.

Веднъж дефинирани, собствените класове за изключения могат да се ползват по същия начин, както и системните изключения.


Собствени изключения – пример


Ще даден един пример за собствено изключение, което се използва при парсва­нето на текстов файл. То съдържа в себе си специфична инфор­мация за проблем, възникнал при парсването – име на файла, номер на ред, съобщение за грешка и изключение-причинител на проблема:

class ParseFileException : ApplicationException

{

private string mFileName;



private long mLineNumber;
public string FileName

{

get



{

return mFileName;

}

}
public long LineNumber



{

get


{

return mLineNumber;

}

}
public ParseFileException(string aMessage, string aFileName,



long aLineNumber, Exception aCauseException) : base(

aMessage, aCauseException)

{

mFileName = aFileName;



mLineNumber = aLineNumber;

}
public ParseFileException(string aMessage, string aFileName,

Exception aCauseException) : this(

aMessage, aFileName, 0, aCauseException)

{

}
public ParseFileException(string aMessage, string aFileName) :



this(aMessage, aFileName, null)

{

}



}

В класа ParseFileException няма нищо сложно. Той наследява System. Exception и дефинира две полета (име на файл и номер на ред), две свойства за достъп до тях и няколко конструктора за инициализация на класа по различен набор от параметри.

Понеже всички инстанции на ParseFileException се създават чрез извикване (директно или инди­ректно) на базовия конструктор на класа ApplicationException, то при подаване на изключение-причинител, то ще бъде записано в свойството InnerException, което се наследява от класа System.Exception. По същия начин подаденото текстово описание на проблема ще се запише в наследеното свойство Message.

Ето как изключението ParseFileException може да бъде използвано в програма, която по даден текстов файл, съдържащ цели числа (по 1 на ред), намира тяхната сума:

static long CalculateSumOfLines(string aFileName)

{

StreamReader inF;



try

{

inF = File.OpenText(aFileName);



}

catch (IOException ioe)

{

throw new ParseFileException(String.Format(



"Can not open the file {0} for reading.",

aFileName), aFileName, ioe);

}
try

{

long sum = 0;



long lineNumber = 0;

while (true)

{

lineNumber++;



string line;

try


{

line = inF.ReadLine();

}

catch (IOException ioe)



{

throw new ParseFileException(

"Error reading from file.",

aFileName, lineNumber, ioe);

}

if (line == null)



break; // end of file reached
try

{

sum += Int32.Parse(line);



}

catch (SystemException se)

{

throw new ParseFileException(String.Format(



"Error parsing line '{0}'.", line),

aFileName, lineNumber, se);

}

}

return sum;



}

finally


{

inF.Close();

}

}
static void Main()



{

try


{

long sumOfLines = CalculateSumOfLines(@"c:\test.txt");

Console.WriteLine("The sum of lines={0}", sumOfLines);

}

catch (ParseFileException pfe)



{

Console.WriteLine("File name: {0}", pfe.FileName);

Console.WriteLine("Line number: {0}", pfe.LineNumber);

Console.WriteLine("Exception: {0}", pfe);

}

}


В кода са използвани класове за работа с текстови файлове и потоци от пространството с имена System.IO, които ще разгледаме подробно в темата "Вход и изход". Засега нека се съсредоточим върху използването на изключения, а не върху работата с файлове.

В примера при възникване на проблем при четенето от файла или с формата на данните, прочетени от него, се хвърля изключението ParseFileException. В него се задава подходящо съобщение за грешка, записват се името на файла, номерът на реда, където е възникнал проблема, и изключението-причи­нител на проблема.

Ако стартираме приложението в момент, в който файлът c:\test.txt липсва, ще получим следния резултат:

File name: c:\test.txt

Line number: 0

Exception: ParseFileException: Can not open the file c:\test.txt for reading. ---> System.IO.FileNotFoundException: Could not find file "c:\test.txt". File name: "c:\test.txt"

at System.IO.__Error.WinIOError(Int32 errorCode, String str)

at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean useAsync, String msgPath, Boolean bFromProxy)

at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)

at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize)

at System.IO.StreamReader..ctor(String path)

at System.IO.File.OpenText(String path)

at Test.CalculateSumOfLines(String aFileName) in c:\demos\ParseFileExceptionDemo.cs:line 52

--- End of inner exception stack trace ---

at Test.CalculateSumOfLines(String aFileName) in c:\demos\ParseFileExceptionDemo.cs:line 56

at Test.Main() in c:\demos\ParseFileExceptionDemo.cs:line 106


Както се вижда, възникнало е изключение ParseFileException, а причи­ната за него е изключението System.IO.FileNotFoundException.

Съхраняването на началната причина за възникване на изключението при пода­ване на изключение от по-високо ниво на абстракция (както в горния пример) е добра практика, защото дава на разработчика по-богата инфор­мация за възникналия проблем.

В примера изключението ParseFileException е от по-високо ниво на абстракция, отколкото FileNotFoundException и дава по-богата информа­ция на разработчика.




Сподели с приятели:
1   ...   33   34   35   36   37   38   39   40   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница