
Объектно-ориентированное программирование.-6
.pdf
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