Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Объектно-ориентированное программирование.-6

.pdf
Скачиваний:
8
Добавлен:
05.02.2023
Размер:
4.5 Mб
Скачать

Exception e3 = new Exception("Мое сообщение", e1);

Console.WriteLine(e1.Message);

Console.WriteLine(e2.Message);

Console.WriteLine(e3.InnerException);

try

{

throw new Exception("Что-то сломалось...");

}

catch (Exception e)

{

Console.WriteLine("Сообщение: {0}", e.Message); Console.WriteLine("Стек вызовов: {0}", e.StackTrace); Console.WriteLine("Источник: {0}", e.Source);

}

Вывод на консоль:

Выдано исключение типа "System.Exception". Мое сообщение

System.Exception: Выдано исключение типа "System.Exception".

Сообщение: Что-то сломалось...

Стек вызовов: в ExceptionSample.Program.Main() в C:\C# Samples\3.4\3_4_5_exception\Program.cs: строка 18

Источник: 3_4_5_exception

Класс System.Exception является базовым для большого количества классов исключений. Рассмотрим некоторые из них:

Exception

├── SystemException

├── ArgumentException

│ ├── ArgumentNullException

│ └── ArgumentOutOfRangeException

├── ArithmeticException

│ ├── DivideByZeroException

│ ├── NotFiniteNumberException

│ └── OverflowException

├── FormatException

│ └── IO.FileFormatException

├── IndexOutOfRangeException

├── InvalidCastException

├── IOException

│ ├── DirectoryNotFoundException

│ ├── EndOfStreamException

│ ├── FileNotFoundException

│ ├── FileLoadException

│ └── PathTooLongException

├── NullReferenceException

├── OutOfMemoryException

│ └── InsufficientMemoryException

├── RankException

├── StackOverflowException

└── ...

└── ...

В принципе, их названия говорят сами за себя. Со многими из них мы

211

уже познакомились, о некоторых других будем говорить далее. Более полную информацию можно получить в библиотеке MSDN.

Большинство унаследованных от System.Exception классов не добавляют функциональности базовому классу. Тогда зачем они были созданы? Причина в том, что один блок try может иметь несколько блоков catch. Это позволяет программе обрабатывать различные исключения в соответствии с их типом.

3.4.5.2. Основной синтаксис

Обработка исключений

При работе с исключениями, как и в языке C++, используются всего четыре ключевых слова: try, catch, finally и throw. Рассмотрим синтаксис операторов блока обработки исключения try, catch и finally:

<блок обработки исключения> :: try <блокT>

catch (<тип1> [<идентификатор1>]) <блокC1> [catch (<тип2> [<идентификатор2>]) <блокC2>] [...]

[catch (<типN> [<идентификаторN>]) <блокCN>]

[catch <блокCU>] [filally <блокF>]

<блок обработки исключения> :: try <блокT>

[catch (<тип1> [<идентификатор1>]) <блокC1>] [catch (<тип2> [<идентификатор2>]) <блокC2>] [...]

[catch (<типN> [<идентификаторN>]) <блокCN>] catch <блокCU>

[filally <блокF>]

<блок обработки исключения> :: try <блокT>

filally <блокF>

Замечания:

1.Блок try присутствует обязательно. Также должен быть хотя бы один блок catch (получаем обработчик исключения типа try-catch), либо блок finally (try-finally), либо оба этих вида блоков одновременно (try-catch-finally).

2.Специальные блоки catch (C1, C2, …, CN) должны располагаться после блока try. Типы данных у идентификаторов должны являться классом System.Exception или его потомками. При этом, <типi> не должен совпадать ни с одним из использованных ранее типов <типj> (j < i), а также не должен являться их потомком.

212

3.Универсальный блок catch (CU), если он описан, должен быть размещен после специальных блоков catch, либо, в случае их отсутствия, после блока try. Он перехватывает все типы исключений, не обработанных ранее. Поэтому, если выше присутствовал специальный блок catch, перехватывающий исключения типа System.Exception, использование универсального блока приведет к предупреждению от компилятора (т.к. он будет недостижим).

4.Блок finally, если он описан, должен быть размещен после всех прочих блоков try и catch.

Схема работы блока обработки исключения приведена на рис. 3.14. Видно, что блок finally, если он описан, выполняется и в том случае, когда исключение возникло, и в том, когда не возникло. Соответственно, в него помещается код, который должен быть выполнен в любом случае (закрытие файлов, освобождение ресурсов и т.п.).

Из всех блоков catch, если они описаны, выполняется только один – специальный (если найден такой <типi>, что E является экземпляром этого класса или его потомка) или универсальный (Ci == CU). Если подходящий специальный блок не найден, а универсальный не описан, либо если исключение в блоке try не возникло, управление ни одному из блоков catch не передается.

Пример:

try {

}

// Ошибка

try {

}

// ОК

catch

(ArgumentException) { } // ОК

catch

(Exception) { } // ОК

try {

}

// ОК

finally

{ } // ОК

catch

(Exception) { } // Ошибка

try {

}

// ОК

catch

(SystemException) { } // ОК

catch

(ArgumentException) { } // Ошибка

try {

}

// ОК

catch

{

} // ОК

catch

(Exception) { } // Ошибка

finally

{ } // ОК

try {

}

// ОК

catch

(Exception) { } // ОК

catch

{

} // Предупреждение

 

 

 

213

блокT

false возникло исключение E

true

false

Сi == СU

||

 

E is типi

i = i + 1

true

идентификаторi = E

true

i = 1

блок Ci

false

 

описан

 

true

 

Ci!= CU

&&

false

идентификаторi задан

блокСi

false

блок F

true

 

 

 

описан

 

блокF

Рис. 3.14 – Схема работы блока обработки исключения

Если в некотором коде возможно появление исключения, но мы не зна-

214

ем его тип, поступаем следующим образом:

try

{

// какой-то код...

int[] array = { 1, 2, 3 };

for (int i = 0; i < 10; i++) array[i] = i;

}

catch (Exception e)

{

Console.WriteLine(e);

}

И видим на консоли класс исключения:

System.IndexOutOfRangeException: Индекс находился вне границ массива. в ExceptionSample.Program.Main() в C:\C#

Samples\3.4\3_4_5_exception\Program.cs:строка 7

Таким образом, в данном случае возникает исключительная ситуация класса IndexOutOfRangeException.

Вложенные блоки исключений

Блоки обработки исключений могут быть вложенными. Если в процессе выполнения блока catch или блока finally, в свою очередь, возникает исключение, оно передается в вышестоящий блок обработки исключительных ситуаций. Если таковой не предусмотрен, то исключение обрабатывается компилятором.

Пример:

try

{

try { }

catch (DivideByZeroException) { } finally { }

}

catch (Exception)

{

try { }

catch (NullReferenceException) { } finally { }

}

finally

{

try { }

catch (Exception) { } finally { }

}

215

Генерация исключительных ситуаций

Синтаксис оператора, генерирующего исключения (throw) был рассмотрен в п. 3.4.4.5:

<оператор throw> :: throw [<выражение>];

Выражение может быть любым (вызов метода, создание экземпляра класса, идентификатор и т.п.), но его тип должен являться классом System.Exception или его потомком. Пример:

SystemException ex = new SystemException ("Что-то сломалось..."); throw ex;

throw new OutOfMemoryException ("Не хватает памяти."); throw ExceptionMethod("Сообщение");

Оператор throw без аргументов может использоваться только в блоке catch. В этом случае вышестоящему обработчику исключительных ситуаций передается этот же экземпляр исключения E, который привел к передаче управления в данный блок catch. Повторная генерация исключений полезна в тех случаях, когда наш код должен предусмотреть какую-то реакцию на ошибку, но вышестоящий обработчик также должен о ней узнать.

Пример:

int x;

int a = 1; int b = 0;

try

{

try

{

x = a / b; // исключение

}

catch (DivideByZeroException)

{

// чтобы переменная не осталась не инициализированной x = 0;

throw;

}

}

catch (Exception e)

{

// внешний обработчик исключения

Console.WriteLine(e.Message);

}

3.4.5.3. Передача исключений из методов

Если при вызове какого-либо метода (принадлежащего текущему или

216

другому классу) возникает необработанное исключение, то доверять результатам работы этого метода уже нельзя. Все сделанные им изменения в данных необходимо откатить назад.

Другое важное преимущество исключений над другими методами обработки ошибок связано с конструкторами. Так как конструктор не может возвращать значения, простого и понятного способа сигнализации конструктора вызывающему методу об ошибке просто нет. Однако исключения здесь можно использовать, поскольку вызывающий метод требует лишь помещения конструктора в блок try.

Пример:

class ExceptionClass

{

public int Value;

public ExceptionClass (int value)

{

Value = 100 / value;

}

public int SetValue(int value)

{

return Value = 100 / value;

}

}

static int Main()

{

ExceptionClass ec1 = new ExceptionClass(2);

try

{

ec1.SetValue(0);

}

catch

{

Console.WriteLine("Исключение в методе

ExceptionClass.SetValue"); ec1.Value = 0;

}

ExceptionClass ec2;

try

{

ec2 = new ExceptionClass(0);

}

catch

{

Console.WriteLine("Исключение в конструкторе

ExceptionClass");

ec2 = null;

}

217

return 0;

}

Мы еще не изучили синтаксис описания классов, а также их конструкторов и прочих членов, это будет сделано в главе 4.

3.4.5.4. Получение собственных классов исключений

Создать новый класс исключений достаточно просто – необходимо описать новый класс, наследующийся от одного из имеющихся классов исключений, определить в нем требуемые конструкторы, перегрузить, если это требуется некоторые члены и, опять же, если это требуется, определить новые члены, специфические для нового класса исключений. О синтаксисе описания класса и его членов мы поговорим в главе 4, а пока просто рассмотрим пример:

class MyException : ApplicationException

{

public MyException() :

base("Новое исключение класса MyException") { } public MyException(string message) : base(message) { }

public MyException(string message, Exception innerException) : base(message, innerException) { }

}

static int Main()

{

try

{

// некоторый код

throw new MyException();

}

catch (MyException e)

{

Console.WriteLine(e.Message);

}

catch

{

// все остальные исключения

}

return 0;

}

218

§3.5. Файловый ввод и вывод

Вязыке C# существует множество классов для чтения и записи файлов различных форматов – текста, изображений, XML, таблиц баз данных и т.д. Здесь мы рассмотрим только базовые возможности по чтению и записи текстовых и бинарных файлов.

3.5.1. Перечень основных классов файлового ввода-вывода

Классы, используемые в файловом вводе и выводе:

System.IO.Directory, System.IO.DirectoryInfo – предоставляют методы создания, перемещения и перечисления в директориях и поддиректориях;

System.IO.DriveInfo – предоставляет методы для доступа к сведениям

одиске;

System.IO.File, System.IO.FileInfo – предоставляют методы создания,

копирования, удаления, перемещения и открытия файлов, а также помогает при создании объектов FileStream;

System.IO.Path – предоставляет методы и свойства для обработки имен каталогов и файлов;

System.IO.Stream – поддерживает произвольный доступ к потокам данных различной природы, а также синхронные и асинхронные операции ввода-вывода.

Мы не будем рассматривать работу с дисками, каталогами и именами файлов. Необходимые сведения можно найти в библиотеке MSDN. Рассмотрим только операции создания, чтения и записи текстовых и двоичных файлов.

3.5.2. Потоковый ввод и вывод

Базовым классом для большинства других классов потокового вводавывода является System.IO.Stream. Это абстрактный класс (см. § 4.6) для любых бинарных (двоичных) потоков данных, т.е. создавать его экземпляры нельзя. Вместо этого создаются экземпляры его потомков, конкретизирующих, куда именно мы хотим записать данные или откуда хотим их считать – из таблицы БД, файла, памяти, сокета HTTP, канала и т.п. Основные его члены перечислены в табл. Д.1 (приложение Д).

Наиболее часто используются следующие его потомки:

219

System.IO.Compression.GZipStream – поток, обеспечивающий чтение

изапись данных, упакованных в формате GZip;

System.IO.FileStream – файловые потоки;

System.IO.MemoryStream – поток, резервным хранилищем которого являются блоки памяти (т.е. позволяет рассматривать блок памяти как бинарный файл для чтения и/или записи).

Их основные члены приведены в табл. Д.2-Д.4 (приложение Д). Как уже было сказано, с помощью данных классов можно обрабатывать потоки любой природы и формата, однако, работать с ними, в силу их универсальности, достаточно сложно. Все данные перед записью необходимо преобразовывать в блоки байтов, а при чтении делать обратное преобразование.

Поэтому для чтения и записи бинарных файлов чаще используются классы System.IO.BinaryReader и System.IO.BinaryWriter (табл. Д.5-Д.6, при-

ложение Д). Они используют в качестве базового потока (т.е. потока, реально осуществляющего чтение данных) экземпляр какого-либо потомка класса IO.Stream, но предоставляют более удобные методы для работы с данными более привычных типов – чисел, символов, строк и т.д.

Для чтения и записи текстовых файлов, соответственно, лучше использовать классы, наследуемые от абстрактных классов System.IO.TextReader и System.IO.TextWriter:

System.IO.StreamReader – считывает символы из потока байтов в определенной кодировке;

System.IO.StringReader – осуществляет чтение из строки (т.е. позволяет рассматривать строку как текстовый файл для чтения);

System.IO.StreamWriter – записывает символы в поток байтов в определенной кодировке;

System.IO.StringWriter – осуществляет запись в строку (т.е. позволяет рассматривать строку как текстовый файл для записи).

Список основных членов данных классов приведен в табл. Д.7-Д.12 (приложение Д).

Пример: Samples\3.5\3_5_2_stream.

3.5.2.1. Чтение и запись текстового файла

Используем наиболее простой способ работы с текстовыми файлами – функциональность классов IO.StreamWriter и IO.StreamReader.

220