Конспект лекций (C#)-unlocked. 1 Основные сведения о C# Особенности языка
Скачать 1.97 Mb.
|
Пример: перечисление дней недели: enum DayOfWeek { Monday, Tuesday=5, Wednesday, Thursday, Friday=7, Saturday, Sunday } DayOfWeek d; d = DayOfWeek.Monday; Если элементам перечисления не назначены значения, то они нумеруются по- следовательно с нуля. Если какому-нибудь элементу назначено значение, то следу- ющий элемент без значения, расположенный сразу за ним, получает значение на 1 больше. Например в приведённом выше примере значения элементов перечисления будут следующими: Monday=0, Tuesday=5, Wednesday=6, Thursday=7, Friday=7, Saturday=8, Sunday=9 Значения, назначаемые элементам перечисления, могут быть абсолютно лю- быми, например: enum WorldWar2Years { Start=1939, End=1945, Stalingrad=1942, Kursk=1943 } 2.18 Обработка исключений Исключительная ситуация (исключение) – это возникновение в программе ошибочной ситуации того или иного рода, например, деление на ноль, попытка пре- образовать в число строку и т.д. В случае возникновения исключения программа прекращает выполнение те- кущего блока и выдаёт сообщение об обнаруженной ошибке. Однако перехват и об- 40 работка возникающих ошибок позволяет улучшить контроль за выполнением про- граммы. В языке С# исключения представлены в виде классов. Все классы исключений являются производными от встроенного в С# класса Exception , являющегося ча- стью пространства имён System Обработка исключительных ситуаций в С# организуется с помощью четырёх ключевых слов: try , catch , throw и finally . Они образуют взаимосвязанную подсистему, в которой применение одного из ключевых слов подразумевает приме- нение другого. 2.18.1 Класс Exception и стандартные исключения Класс Exception , являясь родителем всех классов исключений, предоставля- ет им единые свойства и методы, некоторыми из которых являются: Message – текст, описывающий исключение; StackTrace – позволяет получить стек вызовов для определения места возник- новения исключения; GetType() – возвращает тип исключения. В сочетании с методом ToString() имеется возможность получить строковое представление типа исключения. Некоторые стандартные исключения приведены в таблице 2.5. Таблица 2.5 – Некоторые стандартные исключения Наименование Причина возникновения исключения AccessViolationException попытка чтения или записи в защищённую об- ласть памяти ArithmeticException ошибки в арифметических действий, а также операциях приведения к типу и преобразования DivideByZeroException попытка деления на ноль. Для вещественных чи- сел не возникает, т.к. там используются значения ± бесконечность или нечисловое значение OverflowException переполнение при выполнении арифметических операций, операций приведения типов и преоб- разования FormatException ошибка при преобразовании из одного типа дан- ных в другой, например, при преобразовании из строки в число методами класса Convert IndexOutOfRangeException попытка обращения к элементу массива с индек- сом, который находится вне границ массива InvalidCastException недопустимое приведение или явное преобразо- вание типов IOException ошибки ввода-вывода DirectoryNotFoundException невозможно найти часть файла или каталога 41 Продолжение таблицы 2.5 Наименование Причина возникновения исключения DriveNotFoundException попытка доступа к недоступному диску или данным совместного использования EndOfStreamException попытка выполнить чтение за пределами потока FileNotFoundException попытка доступа к файлу, не существующему на дис- ке PathTooLongException путь или имя файла превышает максимальную длину, определённую системой NullReferenceException попытка использовать пустую ссылку, т.е. ссылку, ко- торая не указывает ни на один из объектов Иерархия некоторых классов исключений имеет вид: Exception SystemException AccessViolationException ArithmeticException DivideByZeroException OverflowException FormatException IndexOutOfRangeException InvalidCastException IOException DirectoryNotFoundException DriveNotFoundException EndOfStreamException FileNotFoundException PathTooLongException NullReferenceException Знание иерархии классов важно для правильной обработки возникающих ис- ключений. 2.18.2 Блок try...catch Основу обработки исключительных ситуаций в С# составляет пара ключевых слов try и catch , формальное использование которых имеет вид: try { <блок кода, проверяемый на ошибки> } catch (<тип исключения 1> [<переменная исключения 1>]) { <обработка исключения типа 1> } [catch (<тип исключения 2> [<переменная исключения 2>]) 42 { <обработка исключения типа 2> } ...] Принцип работы блока try...catch следующий. При возникновении ошиб- ки в блоке <блок кода, проверяемый на ошибки> дальнейшее выполнение данного блока прекращается и управление передаются тому блоку catch (т.е. вы- полняется блок <обработка исключения типа N> ), у которого <тип исключения N> совпадает с типом возникшей ошибки. Если при выполнении блока <обработка исключения типа N> требуется доступ к параметрам исключения, то в блоке catch может быть описана переменная <переменная исключения N> Если в блоке <блок кода, проверяемый на ошибки> не возникло оши- бок, то все блоки catch пропускаются. При использовании нескольких блоков catch все типы исключений, которые они обрабатывают, должны быть разными. При этом, если между двумя типами ис- ключений есть связь «родитель–потомок», то сначала должна быть описана обра- ботка «исключения–потомка». Пример: найти результат целочисленного деления числа 1000 на число a , ко- торое пользователь вводит в компоненте A_TB класса TextBox . Результат вывести в компонент R_TB класса TextBox try { int a = Convert.ToInt32(A_TB.Text); R_TB.Text = String.Format( "Результат выражения 1000/{0} равен {1}", a, 1000/a); } catch (FormatException) { R_TB.Text = "Ошибка: число А должно быть целым"; } catch (DivideByZeroException) { R_TB.Text = "Ошибка: деление на 0"; } Блоки try...catch могут быть вложенными. При этом, если исключение возникает во внутреннем блоке, и там не обрабатывается, то оно передаётся во внешний блок, где может быть обработано 1 Пример: найти результат целочисленного деления числа 1000 на число a , ко- торое пользователь вводит в компоненте A_TB класса TextBox . Результат вывести в компонент R_TB класса TextBox . Если число введено неправильно, то считать его значение равным 1. 1 В общем случае все блоки try...catch можно рассматривать как вложенные, т.к. все они находятся в блоке обработки исключений программы, вставляемом компилятором автоматически. 43 try { int a; try { a = Convert.ToInt32(A_TB.Text); } catch (FormatException) { a = 1; } R_TB.Text = String.Format( "Результат выражения 1000/{0} равен {1}", a, 1000/a); } catch (DivideByZeroException) { R_TB.Text = "Ошибка: деление на 0"; } В блоке catch может не указываться тип исключения. В этом случае такой блок располагается самым последним и обрабатывает все типы исключений, не об- работанные до него. 2.18.3 Блок try...finally Блок try...finally применяется для создания фрагмента кода, который должен выполниться даже в том случае возникновения исключения. Формальная структура данного блока имеет вид: try { <блок кода, проверяемый на ошибки> } finally { <обязательно выполняемый фрагмент кода> } Алгоритм работы блока try...finally следующий. При возникновении ошибки в блоке <блок кода, проверяемый на ошибки> дальнейшее выполне- ние данного блока прекращается и управление передаются блоку finally (т.е. вы- полняется блок <обязательно выполняемый фрагмент кода> ). Если в блоке <блок кода, проверяемый на ошибки> не возникло оши- бок, то управление передаётся блоку finally Следует отметить, что возникающая ошибка останется необработанной, т.е. будет выдано стандартное сообщение о необработанной ошибке. Пример: найти результат целочисленного деления числа 1000 на число a , ко- торое пользователь вводит в компоненте A_TB класса TextBox . Результат вывести в 44 компонент R_TB класса TextBox . Если число введено неправильно или равно 0, то считать его значение равным 1. int a=1; try { a = Convert.ToInt32(A_TB.Text); if (a==0) a = 1; } finally { R_TB.Text = String.Format( "Результат выражения 1000/{0} равен {1}", a, 1000/a); } 2.18.4 Блок try...catch...finally Чтобы не оставалось необработанных ошибок при использовании блока try...finally , он, как правило, применяется в сочетании с блоком try...catch , путём добавления после него блока finally . При этом, в некоторых случаях блоки catch могут остаться пустыми. Пример: найти результат целочисленного деления числа 1000 на число a , ко- торое пользователь вводит в компоненте A_TB класса TextBox . Результат вывести в компонент R_TB класса TextBox . Если число введено неправильно или равно 0, то считать его значение равным 1. int a=1; try { a = Convert.ToInt32(A_TB.Text); if (a==0) a = 1; } catch (Exception) { } finally { R_TB.Text = String.Format( "Результат выражения 1000/{0} равен {1}", a, 1000/a); } 45 2.18.5 Оператор throw В приведённых выше примерах исключения генерировались программой ав- томатически. Однако в некоторых случаях бывает полезным сгенерировать исклю- чение вручную. Формальное описание оператора генерации исключения имеет вид: throw <объект класса исключения>; Во многих случаях генерация исключения объединяется с созданием объекта, например: throw new Exception("Неправильно введены данные"); Пример: найти результат целочисленного деления числа 1000 на число a , ко- торое пользователь вводит в компоненте A_TB класса TextBox . Число a должно быть в диапазоне -100...100. Результат вывести в компонент R_TB класса TextBox try { int a = Convert.ToInt32(A_TB.Text); if (a < -100 || a > 100) throw new Exception("Число А должно быть в диапазоне -100...100"); R_TB.Text = String.Format( "Результат выражения 1000/{0} равен {1}", a, 1000/a); } catch (FormatException) { R_TB.Text = "Ошибка: число А должно быть целым"; } catch (DivideByZeroException) { R_TB.Text = "Ошибка: деление на 0"; } catch (Exception E) { R_TB.Text = E.Message; } С помощью оператора throw можно повторно сгенерировать исключение по- сле его обработки в блоке catch для того, чтобы оно также было обработано во внешнем блоке catch . Для этого во внутреннем блоке catch последним операто- ром указывается оператор throw без параметров. Например, предыдущий пример может быть записан следующим образом: try { int a=0; try { a = Convert.ToInt32(A_TB.Text); 46 if (a < -100 || a > 100) throw new Exception( "Число А должно быть в диапазоне -100...100"); } catch (FormatException) { MessageBox.Show("Ошибка: число А должно быть целым"); throw; } catch (Exception E) { MessageBox.Show(E.Message); throw; } R_TB.Text = String.Format( "Результат выражения 1000/{0} равен {1}", a, 1000/a); } catch (Exception) { R_TB.Text = "Результат не найден из-за неправильного ввода числа А"; } 47 3 Классы. Основные понятия Класс представляет собой тип, содержащий данные, а также методы, позволя- ющие оперировать этими данными. Все элементы, входящие в класс будем называть членами класса. Для каждого объекта класса создаётся своя копия членов-данных, в то время, как члены-методы являются общими для всех объектов. 3.1 Общая схема Общая схема определения класса имеет вид: [<доступ>] class <идентификатор класса> [: <идентификатор класса- родителя>] { <члены класса> } где: <доступ> – спецификатор доступа и/или модификаторы; <идентификатор класса> – идентификатор создаваемого класса; <идентификатор класса-родителя> – идентификатор класса, от которого наследует свойства и поведение создаваемый класс; <члены класса> – поля, методы, свойства и другие структурные единицы клас- са. 3.2 Спецификаторы доступа При объявлении класса или члена класса могут указываться ключевые слова, определяющие тип доступа к ним (спецификаторы доступа): public – доступен без каких-либо ограничений; protected – доступен внутри тела класса, а также для потомков класса; internal – доступен только внутри файлов одной и той же сборки. Внутренний доступ чаще всего используется в разработке на основе компонентов, так как он позволяет группе компонентов взаимодействовать в закрытой форме, не открывая доступ остальной части кода приложения; protected internal – комбинация доступа protected и internal ; private – доступен только внутри тела класса, в котором он объявлен. По умолчанию (т.е. если нет явного указания), члены класса имеют доступ private , а классы – internal ; Спецификаторы доступа protected и private не могут применяться к клас- сам верхнего уровня, т.е. классам, не входящим в состав других классов. 48 Поля класса объявляются, как правило, с помощью спецификаторов доступа protected и private , т.к. доступ к данным класса извне реализуется с помощью свойств, методов или индексаторов. Использование для доступа к полям специфика- тора public снижает надёжность программы 1 3.3 Поля Поле по своей сути представляет обычную переменную некоторого типа, для которой задан спецификатор доступа. Формальная схема описания полей имеет вид: [<доступ>] <тип> <идентификатор 1>[=<значение идентификатора 1>][, <идентификатор2>[=<значение идентификатора 2>] ...]; Примеры: private double d; protected int a, b=10; Начальное значение поля может быть задано через конструктор класса, при этом порядок задания начальных значений следующий: присвоение полю значения по умолчанию для его типа; присвоение значения инициализации, заданного при объявлении поля; присвоение значения, указанного в конструкторе. 3.4 Создание объекта и доступ к его членам В качестве начального примера работы с классами возьмём класс, описанный следующим образом: class Figure { public string sType; // строковое представление типа фигуры public int posX, posY; // координаты фигуры } Объявление переменной, которая может ссылаться на экземпляр некоторого класса (объект) выполняется обычным для переменных способом, например: Figure firstFigure; Классы являются ссылочным типом, поэтому перед использованием перемен- ных данного типа требуется либо задание ссылки на существующий объект, либо созданием объекта путём использования оператора new совместно с вызовом специ- ального метода – конструктора класса, Простейший конструктор (конструктор по 1 В приводимых в конспекте примерах для уменьшения объёма кода и простоты изложения использование полей со спецификатором доступа public будем считать допустимым. 49 умолчанию) вызывается путём указания имени класса с круглыми скобками после него, например: firstFigure = new Figure(); Часто операции по объявлению переменной и созданию объекта объединяют- ся, например: Figure firstFigure = new Figure(); Для обращения к члену класса используется оператор доступа «.», разделяю- щий идентификатор объекта и идентификатор члена, к которому осуществляется доступ. Например, задать строковое представление типа фигуры можно следующим образом: firstFigure.sType = "Квадрат"; 3.5 Методы Хотя классы, содержащие только данные, вполне допустимы, у большинства классов должны быть также методы. Методы представляют собой подпрограммы, которые манипулируют данными, определёнными в классе, а во многих случаях они предоставляют доступ к этим данным. Как правило, другие части программы взаи- модействуют с классом посредством его методов. Формальная схема описания метода имеет вид: [<доступ>] <возвращаемый тип> <метод>([<список параметров>]) { <тело метода> } где: <доступ> – спецификатор доступа и/или модификаторы; <возвращаемый тип> – тип данных, возвращаемых методом. Если метод не воз- вращает данных, то указывается ключевое слово void ; <метод> – идентификатор создаваемого метода; <список параметров> – список параметров, передаваемых в метод или возвра- щаемых им; <тело метода> – код, определяющий набор действий, выполняемых методом. В списке параметров, параметры разделяются запятыми. Описание каждого параметра имеет вид: [<модификатор параметра>] <тип> <идентификатор параметра> где: <модификатор параметра> – ключевое слово, определяющее способы работы с параметром; 50 <тип> – тип данных, к которому принадлежат параметр; <идентификатор параметра> – идентификатор, под которым параметр будет известен внутри метода. Пример: дополним класс Figure методами, позволяющими оперировать с полем sType (а также заблокируем прямой доступ к полю sType путём задания для него спецификатора доступа private и укажем его начальное значение). class Figure { private string sType="Не задан"; public int posX, posY; public string GetSType() { return sType; } public void SetSType(string n_sType) { if (n_sType.Trim() != "") sType = n_sType.Trim(); } } Каждый метод, у которого возвращаемый тип отличен от void , должен со- держать оператор return (или несколько таких операторов для каждой ветви раз- ветвляющегося кода), с указанием значения, которое возвращается методом. Ука- занное значение должно быть совместимо с возвращаемым типом. Например, в ме- тоде GetSType() возвращаемым типом является string , поэтому указание в каче- стве возвращаемого значения текущего значения поля sType вполне допустимо. Вызов оператора return приводит к |