- •1. Лекция: Visual Studio .Net, Framework .Net
- •Visual Studio .Net - открытая среда разработки
- •Открытость
- •Framework .Net - единый каркас среды разработки
- •Библиотека классов FCL - статический компонент каркаса
- •Единство каркаса
- •Встроенные примитивные типы
- •Структурные типы
- •Архитектура приложений
- •Модульность
- •Общеязыковая исполнительная среда CLR - динамический компонент каркаса
- •Двухэтапная компиляция. Управляемый модуль и управляемый код
- •Виртуальная машина
- •Дизассемблер и ассемблер
- •Метаданные
- •Сборщик мусора - Garbage Collector - и управление памятью
- •Исключительные ситуации
- •События
- •Общие спецификации и совместимые модули
- •2. Лекция: Язык C# и первые проекты
- •Создание C#
- •Виды проектов
- •Консольный проект
- •Windows-проект
- •Начало начал - точка "большого взрыва"
- •Выполнение проекта по умолчанию после "большого взрыва"
- •Проект WindowsHello
- •3. Лекция: Система типов языка С#
- •Общий взгляд
- •Система типов
- •Типы или классы? И типы, и классы
- •Семантика присваивания
- •Преобразование к типу object
- •Примеры преобразований
- •Семантика присваивания. Преобразования между ссылочными и значимыми типами
- •Операции "упаковать" и "распаковать" (boxing и unboxing).
- •4. Лекция: Преобразования типов
- •Где, как и когда выполняются преобразования типов?
- •Преобразования ссылочных типов
- •Преобразования типов в выражениях
- •Преобразования внутри арифметического типа
- •Явные преобразования
- •Преобразования строкового типа
- •Преобразования и класс Convert
- •Проверяемые преобразования
- •Исключения и охраняемые блоки. Первое знакомство
- •Опасные вычисления в охраняемых проверяемых блоках
- •Опасные вычисления в охраняемых непроверяемых блоках
- •Опасные преобразования и методы класса Convert
- •5. Лекция: Переменные и выражения
- •Объявление переменных
- •Проект Variables
- •Синтаксис объявления
- •Время жизни и область видимости переменных
- •Поля
- •Локальные переменные
- •Глобальные переменные уровня процедуры. Существуют ли?
- •Константы
- •6. Лекция: Выражения. Операции в выражениях
- •Выражения
- •Приоритет и порядок выполнения операций
- •Перегрузка операций
- •С чего начинается выполнение выражения
- •Операции "увеличить" и "уменьшить" (increment, decrement)
- •Операции sizeof и typeof
- •Как получить подробную информацию о классе?
- •Статические поля и методы арифметических классов
- •Операция new
- •Арифметические операции
- •Операции отношения
- •Операции проверки типов
- •Операции сдвига
- •Логические операции
- •Условное выражение
- •Операция приведения к типу
- •7. Лекция: Присваивание и встроенные функции
- •Присваивание
- •Специальные случаи присваивания
- •Определенное присваивание
- •Еще раз о семантике присваивания
- •Рассмотрим объявления:
- •Класс Math и его функции
- •Класс Random и его функции
- •8. Лекция: Операторы языка C#
- •Операторы языка C#
- •Оператор присваивания
- •Блок или составной оператор
- •Пустой оператор
- •Операторы выбора
- •Оператор if
- •Оператор switch
- •Операторы перехода
- •Оператор goto
- •Операторы break и continue
- •Оператор return
- •Операторы цикла
- •Оператор for
- •Циклы While
- •Цикл foreach
- •9. Лекция: Процедуры и функции - методы класса
- •Процедуры и функции - функциональные модули
- •Процедуры и функции - методы класса
- •Процедуры и функции. Отличия
- •Описание методов (процедур и функций). Синтаксис
- •Список формальных аргументов
- •Тело метода
- •Вызов метода. Синтаксис
- •О соответствии списков формальных и фактических аргументов
- •Вызов метода. Семантика
- •Что нужно знать о методах?
- •Почему у методов мало аргументов?
- •Поля класса или функции без аргументов?
- •Пример: две версии класса Account
- •Функции с побочным эффектом
- •Методы. Перегрузка
- •10. Лекция: Корректность методов. Рекурсия
- •Корректность методов
- •Инварианты и варианты цикла
- •Рекурсия
- •Рекурсивное решение задачи "Ханойские башни"
- •Быстрая сортировка Хоара
- •11. Лекция: Массивы языка C#
- •Общий взгляд
- •Объявление массивов
- •Объявление одномерных массивов
- •Динамические массивы
- •Многомерные массивы
- •Массивы массивов
- •Процедуры и массивы
- •12. Лекция: Класс Array и новые возможности массивов
- •Класс Array
- •Массивы как коллекции
- •Сортировка и поиск. Статические методы класса Array
- •Класс Object и массивы
- •Массивы объектов
- •Массивы. Семантика присваивания
- •13. Лекция: Символы и строки постоянной длины в C#
- •Общий взгляд
- •Строки С++
- •Строки С#
- •Класс char
- •Класс char[] - массив символов
- •Существует ли в C# тип char*
- •14. Лекция: Строки C#. Классы String и StringBuilder
- •Класс String
- •Объявление строк. Конструкторы класса string
- •Операции над строками
- •Строковые константы
- •Неизменяемый класс string
- •Статические свойства и методы класса String
- •Метод Format
- •Методы Join и Split
- •Динамические методы класса String
- •Класс StringBuilder - построитель строк
- •Объявление строк. Конструкторы класса StringBuilder
- •Операции над строками
- •Основные методы
- •Емкость буфера
- •15. Лекция: Регулярные выражения
- •Пространство имен RegularExpression и классы регулярных выражений
- •Немного теории
- •Синтаксис регулярных выражений
- •Классы Group и GroupCollection
- •Классы Capture и CaptureCollection
- •Перечисление RegexOptions
- •Класс RegexCompilationInfo
- •Примеры работы с регулярными выражениями
- •Пример "чет и нечет"
- •Пример "око и рококо"
- •Пример "кок и кук"
- •Пример "обратные ссылки"
- •Пример "Дом Джека"
- •Пример "Атрибуты"
- •16. Лекция: Классы
- •Классы и ООП
- •Две роли классов
- •Синтаксис класса
- •Поля класса
- •Доступ к полям
- •Методы класса
- •Доступ к методам
- •Методы-свойства
- •Индексаторы
- •Операции
- •Статические поля и методы класса
- •Константы
- •Конструкторы класса
- •Деструкторы класса
- •Проектирование класса Rational
- •Свойства класса Rational
- •Конструкторы класса Rational
- •Методы класса Rational
- •Закрытый метод НОД
- •Печать рациональных чисел
- •Тестирование создания рациональных чисел
- •Операции над рациональными числами
- •Константы класса Rational
- •17. Лекция: Структуры и перечисления
- •Развернутые и ссылочные типы
- •Классы и структуры
- •Структуры
- •Синтаксис структур
- •Класс Rational или структура Rational
- •Встроенные структуры
- •Еще раз о двух семантиках присваивания
- •Перечисления
- •Персоны и профессии
- •18. Лекция: Отношения между классами. Клиенты и наследники
- •Отношения между классами
- •Отношения "является" и "имеет"
- •Отношение вложенности
- •Расширение определения клиента класса
- •Отношения между клиентами и поставщиками
- •Сам себе клиент
- •Наследование
- •Добавление полей потомком
- •Конструкторы родителей и потомков
- •Добавление методов и изменение методов родителя
- •Статический контроль типов и динамическое связывание
- •Три механизма, обеспечивающие полиморфизм
- •Пример работы с полиморфным семейством классов
- •Абстрактные классы
- •Классы без потомков
- •19. Лекция: Интерфейсы. Множественное наследование
- •Интерфейсы
- •Две стратегии реализации интерфейса
- •Преобразование к классу интерфейса
- •Проблемы множественного наследования
- •Коллизия имен
- •Наследование от общего предка
- •Встроенные интерфейсы
- •Упорядоченность объектов и интерфейс IComparable
- •Клонирование и интерфейс ICloneable
- •Сериализация объектов
- •Класс с атрибутом сериализации
- •Интерфейс ISerializable
- •20. Лекция: Функциональный тип в C#. Делегаты
- •Как определяется функциональный тип и как появляются его экземпляры
- •Функции высших порядков
- •Вычисление интеграла
- •Построение программных систем методом "раскрутки". Функции обратного вызова
- •Наследование и полиморфизм - альтернатива обратному вызову
- •Делегаты как свойства
- •Операции над делегатами. Класс Delegate
- •Операции "+" и "-"
- •Пример "Комбинирование делегатов"
- •Пример "Плохая служба"
- •21. Лекция: События
- •Классы с событиями
- •Класс sender. Как объявляются события?
- •Делегаты и события
- •Как зажигаются события
- •Классы receiver. Как обрабатываются события
- •Классы с событиями, допустимые в каркасе .Net Framework
- •Пример "Списки с событиями"
- •Класс sender
- •Классы receiver
- •Две проблемы с обработчиками событий
- •Игнорирование коллег
- •Переопределение значений аргументов события
- •Классы с большим числом событий
- •Проект "Город и его службы"
- •22. Лекция: Универсальность. Классы с родовыми параметрами
- •Наследование и универсальность
- •Синтаксис универсального класса
- •Класс с универсальными методами
- •Два основных механизма объектной технологии
- •Стек. От абстрактного, универсального класса к конкретным версиям
- •Ограниченная универсальность
- •Синтаксис ограничений
- •Список с возможностью поиска элементов по ключу
- •Как справиться с арифметикой
- •Родовое порождение класса. Предложение using
- •Универсальность и специальные случаи классов
- •Универсальные структуры
- •Универсальные интерфейсы
- •Универсальные делегаты
- •Framework .Net и универсальность
- •23. Лекция: Отладка и обработка исключительных ситуаций
- •Корректность и устойчивость программных систем
- •Жизненный цикл программной системы
- •Три закона программотехники
- •Первый закон (закон для разработчика)
- •Второй закон (закон для пользователя)
- •Третий закон (закон чечако)
- •Отладка
- •Создание надежного кода
- •Искусство отладки
- •Отладочная печать и условная компиляция
- •Классы Debug и Trace
- •Метод Флойда и утверждения Assert
- •Классы StackTrace и BooleanSwitch
- •Отладка и инструментальная среда Visual Studio .Net
- •Обработка исключительных ситуаций
- •Обработка исключений в языках C/C++
- •Схема обработки исключений в C#
- •Выбрасывание исключений. Создание объектов Exception
- •Захват исключения
- •Параллельная работа обработчиков исключений
- •Блок finally
- •Схема Бертрана обработки исключительных ситуаций
- •Класс Exception
- •Организация интерфейса
- •Форма и элементы управления
- •Взаимодействие форм
- •Модальные и немодальные формы
- •Передача информации между формами
- •Образцы форм
- •Главная кнопочная форма
- •Шаблон формы для работы с классом
- •Работа со списками (еще один шаблон)
- •Элемент управления класса ListBox
- •Наследование форм
- •Два наследника формы TwoLists
- •Огранизация меню в формах
- •Создание меню в режиме проектирования
- •Классы меню
- •Создание инструментальной панели с командными кнопками
- •Рисование в форме
- •Класс Graphics
- •Методы класса Graphics
- •Класс Pen
- •Класс Brush
- •Проект "Паутина Безье, кисти и краски"
- •Паутина Безье
- •Событие Paint
- •Кисти и краски
- •Абстрактный класс Figure
- •Классы семейства геометрических фигур
- •Класс Ellipse
- •Класс Circle
- •Класс LittleCircle
- •Класс Rect
- •Класс Square
- •Класс Person
- •Список с курсором. Динамические структуры данных
- •Классы элементов списка
- •Организация интерфейса
Рис. 23.4. Файл с записью сообщения о нарушении утверждения Assert
Вариацией метода Assert является метод Fail, всегда приводящий к появлению окна с сообщением о нарушении утверждения, проверка которого осуществляется обычным программным путем.
Классы StackTrace и BooleanSwitch
В библиотеке FCL имеются и другие классы, полезные при отладке. Класс StackTrace позволяет получить программный доступ к стеку вызовов. Класс BooleanSwitch предоставляет механизм, аналогичный константам условной компиляции. Он разрешает определять константы, используемые позже в методе условной печати WriteIf классов Debug и Trace. Мощь этого механизма в том, что константы можно менять в файле конфигурации проекта, не изменяя код проекта и не требуя его перекомпиляции.
Отладка и инструментальная среда Visual Studio .Net
Инструментальная среда студии предоставляет программисту самый широкий спектр возможностей слежения за ходом вычислений и отслеживания состояний, в котором находится процесс вычислений. Поскольку все современные инструментальные среды организованы сходным образом и хорошо известны работающим программистам, я позволю себе не останавливаться на описании возможностей среды.
Обработка исключительных ситуаций
Какой бы надежный код ни был написан, сколь бы тщательной ни была отладка, в версии, переданной в эксплуатацию и на сопровождение, при запусках будут встречаться нарушения спецификаций. Причиной этого являются выше упомянутые законы программотехники. В системе остается последняя ошибка, находятся пользователи, не знающие спецификаций, и если спецификацию можно нарушить, то это событие когда-нибудь да произойдет. В таких исключительных ситуациях продолжение выполнения программы либо становится невозможным (попытка выполнить неразрешенную операцию деления на ноль, попытки записи в защищенную область памяти, попытка открытия несуществующего файла, попытка получить несуществующую запись базы данных), либо в возникшей ситуации применение алгоритма приведет к ошибочным результатам.
Что делать при возникновении исключительной ситуации? Конечно, всегда есть стандартный способ - сообщить о возникшей ошибке и прервать выполнение программы. Понятно, что это приемлемо лишь для безобидных приложений; даже для компьютерных игр этот способ не годится, что уж говорить о критически важных приложениях!
В языках программирования для обработки исключительных ситуаций предлагались самые разные подходы.
Обработка исключений в языках C/C++
Для стиля программирования на языке C характерно описание методов класса как булевых функций, возвращающих true в случае нормального завершения метода и
false - при возникновении исключительной ситуации. Вызов метода встраивался в If- оператор, обрабатывающий ошибку в случае неуспеха завершения метода:
bool MyMethod(...){...}
if !MyMethod(){// обработка ошибки} {//нормальное выполнение}
Недостатки этой схемы понятны. Во-первых, мало информации о причине возникновения ошибки, поэтому либо через поля класса, либо через аргументы метода нужно передавать дополнительную информацию. Во-вторых, блок обработки встраивается в каждый вызов, что приводит к раздуванию кода.
Поэтому в C/C++ применяется схема try/catch блоков, суть которой в следующем. Участок программы, в котором может возникнуть исключительная ситуация, оформляется в виде охраняемого try-блока. Если при его выполнении возникает исключительная ситуация, то происходит прерывание выполнения try-блока c классификацией исключения. Это исключение начинает обрабатывать один из catchблоков, соответствующий типу исключения. В C/C++ применяются две такие схемы. Одна из них - схема с возобновлением - соответствует так называемым структурным, или С-исключениям. Вторая схема - без возобновления - соответствует С++ исключениям. В первой схеме обработчик исключения - catch-блок - возвращает управление в некоторую точку try-блока. Во второй схеме управление не возвращается
вtry-блок.
Снекоторыми синтаксическими отличиями схема с возобновлением применяется в языках VB/VBA.
Многообразие подходов к обработке исключений говорит о том, что не найден единый, удовлетворяющий всех подход. Чуть позже я расскажу о наиболее разумной, с моей точки зрения, схеме. К сожалению, в C# применяется не она.
Схема обработки исключений в C#
Язык C# наследовал схему исключений языка С++, внеся в нее свои коррективы. Рассмотрим схему подробнее и начнем с синтаксиса конструкции try-catch-finally:
try {...}
catch (T1 e1) {...}
...
catch(Tk ek) {...} finally {...}
Всюду в тексте модуля, где синтаксически допускается использование блока, этот блок можно сделать охраняемым, добавив ключевое слово try. Вслед за try-блоком могут следовать catch-блоки, называемые блоками-обработчиками исключительных ситуаций, их может быть несколько, они могут и отсутствовать. Завершает эту последовательность finally-блок - блок финализации, который также может отсутствовать. Вся эта конструкция может быть вложенной - в состав try-блока может входить конструкция try-catch-finally.
Выбрасывание исключений. Создание объектов Exception
В теле try-блока может возникнуть исключительная ситуация, приводящая к
выбрасыванию исключений. Формально выбрасывание исключения происходит при выполнении оператора throw. Этот оператор, чаще всего, выполняется в недрах операционной системы, когда система команд или функция API не может сделать свою работу. Но этот оператор может быть частью программного текста try-блока и
выполняться, когда в результате проведенного анализа становится понятным, что дальнейшая нормальная работа невозможна.
Синтаксически оператор throw имеет вид:
throw[выражение]
Выражение throw задает объект класса, являющегося наследником класса Exception. Обычно это выражение new, создающее новый объект. Если оно отсутствует, то повторно выбрасывается текущее исключение. Если исключение выбрасывается операционной системой, то она сама классифицирует исключение, создает объект соответствующего класса и автоматически заполняет его поля.
В рассматриваемой нами модели исключения являются объектами, класс которых представляет собой наследника класса Exception. Этот класс и многочисленные его наследники является частью библиотеки FCL, хотя и разбросаны по разным пространствам имен. Каждый класс задает определенный тип исключения в соответствии с классификацией, принятой в Framework .Net. Вот лишь некоторые
классы исключений из пространства имен System: Argument Exception,
ArgumentOutOfRangeException, ArithmeticException, BadImageFormatException, DivideByZeroException, OverflowException. В пространстве имен System.IO собраны
классы исключений, связанных с проблемами ввода-вывода:
DirectoryNotFoundException, FileNotFoundException и многие другие. Имена всех
классов исключений заканчиваются словом Exception. Разрешается создавать собственные классы исключений, наследуя их от класса Exception.
При выполнении оператора throw создается объект te, класс TE которого характеризует текущее исключение, а поля содержат информацию о возникшей исключительной ситуации. Выполнение оператора throw приводит к тому, что нормальный процесс вычислений на этом прекращается. Если это происходит в охраняемом try-блоке, то начинается этап "захвата" исключения одним из обработчиков исключений.
Захват исключения
Блок catch - обработчик исключения имеет следующий синтаксис:
catch (T e) {...}
Класс T,T указанный в заголовке catch-блока, должен принадлежать классам исключений. Блок catch с формальным аргументом e класса TT потенциально способен захватить текущее исключение te класса TE, если и только если объект te совместим по присваиванию c объектом e. Другими словами, потенциальная способность захвата означает допустимость присваивания e = te, что возможно, когда класс TE является потомком класса T.T Обработчик, класс TT которого является классом Exception, является универсальным обработчиком, потенциально он способен захватить любое исключение, поскольку все они являются его потомками.
Потенциальных захватчиков может быть много, исключение захватывает лишь один - тот из них, кто стоит первым в списке проверки. Каков порядок проверки? Он довольно естественный. Вначале проверяются обработчики в порядке следования их за try- блоком, и первый потенциальный захватчик становится активным, захватывая исключение и выполняя его обработку. Отсюда становится ясно, что порядок следования в списке catch-блоков крайне важен. Первыми идут наиболее специализированные обработчики, далее по мере возрастания универсальности. Так, вначале должен идти обработчик исключения DivideByZeroException, а уже за ним -
ArithmeticException. Универсальный обработчик, если он есть, должен стоять последним. За этим наблюдает статический контроль типов. Если потенциальных захватчиков в списке catch-блоков нет (сам список может отсутствовать), то происходит переход к списку обработчиков охватывающего блока. Напомню, что try- блок может быть вложен в другой try-блок. Когда же будет исчерпаны списки вложенных блоков, а потенциальный захватчик не будет найден, то произойдет подъем по стеку вызовов. На рис. 23.5 показана цепочка вызовов, начинающаяся с точки "большого взрыва" - процедуры Main.
Рис. 23.5. Цепочка вызовов, хранящаяся в стеке вызовов
О точке большого взрыва и цепочке вызовов мы говорили еще в лекции 2.
Исключение возникло в последнем вызванном методе цепочки - на рисунке метод r5. Если у этого метода не нашлось обработчиков события, способных обработать исключение, то это пытается сделать метод r4, вызвавший r5. Если вызов r5 находится в охраняемом блоке метода r4, то начнет проверяться список обработчиков в охраняемом блоке метода r4. Этот процесс подъема по списку вызовов будет продолжаться, пока не будет найден обработчик, способный захватить исключение, или не будет достигнута начальная точка - процедура Main. Если и в ней нет потенциального захватчика исключения, то сработает стандартный обработчик, прерывающий выполнение программы с выдачей соответствующего сообщения.
Параллельная работа обработчиков исключений
Обработчику исключения - catch-блоку, захватившему исключение, - передается текущее исключение. Анализируя свойства этого объекта, обработчик может понять причину, приведшую к возникновению исключительной ситуации, попытаться ее исправить и в случае успеха продолжить вычисления. Заметьте, в принятой C# схеме без возобновления обработчик исключения не возвращает управление try-блоку, а сам пытается решить проблемы. После завершения catch-блока выполняются операторы текущего метода, следующие за конструкцией try-catch-finally.
Зачастую обработчик исключения не может исправить ситуацию или может выполнить это лишь частично, предоставив решение оставшейся части проблем вызвавшему методу - предшественнику в цепочке вызовов. Механизм, реализующий такую возможность - это тот же механизм исключений. Как правило, в конце своей работы обработчик исключения выбрасывает исключение, выполняя оператор throw. При этом у него есть две возможности: повторно выбросить текущее исключение или выбросить новое исключение, содержащее дополнительную информацию.
Некоторые детали будут пояснены позже при рассмотрении примеров.
Таким образом, обработку возникшей исключительной ситуации могут выполнять несколько обработчиков, принадлежащие разным уровням цепочки вызовов.