C# отлов исключения открытия папки. Определение собственных классов исключений. Обработка исключений и условные конструкции

константам условной компиляции . Он разрешает определять константы, используемые позже в методе условной печати 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# наследовал схему исключений языка С++, внеся в нее свои коррективы. Рассмотрим схему подробнее и начнем с синтаксиса конструкции 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 : ArgumentException , ArgumentOutOfRangeException , ArithmeticException , BadImageFormatException , DivideByZeroException , OverflowException . В пространстве имен System.IO собраны классы исключений , связанных с проблемами ввода-вывода: DirectoryNotFoundException , FileNotFoundException и многие другие. Имена всех классов исключений заканчиваются словом Exception . Разрешается создавать собственные классы исключений , наследуя их от класса Exception .

При выполнении оператора throw создается объект te , класс TE которого характеризует текущее исключение, а поля содержат информацию о возникшей исключительной ситуации . Выполнение оператора throw приводит к тому, что нормальный процесс вычислений на этом прекращается. Если это происходит в охраняемом try-блоке, то начинается этап "захвата" исключения одним из обработчиков исключений.

Захват исключения

Блок catch - обработчик исключения имеет следующий синтаксис:

catch (T e) {...}

Класс T , указанный в заголовке catch -блока, должен принадлежать классам исключений . Блок catch с формальным аргументом e класса T потенциально способен захватить текущее исключение te класса TE , если и только если объект te совместим по присваиванию c объектом e. Другими словами, потенциальная способность захвата означает допустимость присваивания e = te , что возможно, когда класс TE является потомком класса T . Обработчик, класс T которого является классом Exception , является универсальным обработчиком , потенциально он способен захватить любое исключение, поскольку все они являются его потомками.

Потенциальных захватчиков может быть много, исключение захватывает лишь один - тот из них, кто стоит первым в списке проверки. Каков порядок проверки? Он довольно естественный. Вначале проверяются обработчики в порядке следования их за try -блоком, и первый потенциальный захватчик становится активным, захватывая исключение и выполняя его обработку. Отсюда становится ясно, что порядок следования в списке catch -блоков крайне важен. Первыми идут наиболее специализированные обработчики , далее по мере возрастания универсальности. Так, вначале должен идти обработчик исключения DivideByZeroException , а уже за ним - Main . Если и в ней нет потенциального захватчика исключения, то сработает стандартный обработчик, прерывающий выполнение программы с выдачей соответствующего сообщения.

Исключение является экземпляром класса, который был явно и неявно унаследован от базового класса System.Exception.

Переменные, объявленные в блоке try выходят из видимости, когда управление предается в блок catch или finally. По окончании блока try, даже если ничего не произошло (ошибка не возникла), управление автоматически передается в блок finally, который должен содержать инструкции для освобождения ресурсов.

При обнаружении ошибки код осуществляет генерацию исключения (создание экземпляра класса исключения) и выдает его следующим образом:

Throw new IndexOutOfRangeException(“Вы ввели: ” + userInput);

Как только компилятор встречает оператор throw внутри блока try, он немедленно ищет соответствующий блок catch.

Операторы throw не обязательно должны находится в том же методе, что и блок try, блок try продолжает действовать даже при передаче управления другим методам.

Оператор throw может находиться в любом методе, вызванном во время выполнения блока try, — оно не обязано располагаться в том же самом методе, в котором определен блок try

Обработчики исключений для производного класса (IndexOutOfRangeException) должны идти раньше, чем для базового (Exception) (!)

Если обработчик catch записан как: catch { … } то это значит, что он отвечает за любой код (за любое возникшее исключение), в том числе написанным не на C# или не управляемым.

То, что в качестве исключений могут быть переданы только экземпляры класса, производного от System.Exception является требованием C#.

While(true) // бесконечный цикл { Console.Write("Выберите пункт меню от 0 до 5 или для выхода: "); string userInput = Console.ReadLine(); if (userInput == "") {Console.WriteLine("Вы нажали значт выходим..."); break; } try { int x = int.Parse(userInput); switch (x) { case 0: Console.WriteLine("Выбран пункт 0"); break; case 1: Console.WriteLine("Выбран пункт 1"); break; case 2: Console.WriteLine("Выбран пункт 2"); break; case 3: Console.WriteLine("Выбран пункт 3"); break; case 4: Console.WriteLine("Выбран пункт 4"); break; case 5: Console.WriteLine("Выбран пункт 5"); break; default: throw new IndexOutOfRangeException(); break; } } catch (IndexOutOfRangeException e) { Console.WriteLine("Неверное значение. Выберити цифру от "); } catch (FormatException e) { Console.WriteLine("Ошибка преобразования, возможно вы ввели строку..."); } catch (Exception e) { Console.WriteLine("Еще какая-то ошибка"); } catch {} // обработчик неуправляемого кода и кода на другом языке }

Свойства и методы System.Exception

if (ErrorCondition == true) { Exception myException = new ClassMyException("Help!"); myException.Source = "Название приложения"; myException.HelpLink = "myHelpFile.txt"; throw myException; }

В коде не нужно генерировать исключения от общего класса System.Exception – он не дает представления о природе ошибочного состояния (!). В иерархии существует два важных класса:

SystemException – предназначен для исключений, обычно генерируемых средой исполнения.NET, или исключений имеющих общую природу и могут быть сгенерированы практически любым приложением. Подкласс представляет как фатальные, так и некритичные ошибки.

ApplicationException – представляет собой базу, предназначенную для любого класса исключений, определенного третьими лицами.

IOException (пространство имен System.IO) связаны с чтением и записью данных в файл.

StackOverflowException возникает тогда, когда участок памяти отведенный под стек, заполняется до отказа. Переполнение стека может возникнуть например в том случае, когда метод начинает рекурсивно вызвать самого себя. Обычной причиной появления EndOfStreamException является попытка чтения за границами файла. Переполнение OverflowException возникает, например при попытке привести int, содержащий -40 к типу uint в контексте checked.

Вложенные боли try используются по двум причинам:

— с целью изменения типа сгенерированного исключения (использование свойства InnerException, которое содержит ссылку на любое сгенерированное исключение);

— обработка разных исключений в разных участках кода.

Определение собственных классов исключений

Определение собственных исключений довольно простая задача, т.к. редко требуется добавлять в них свои собственные методы. Необходимо только реализовать конструктор, для того чтобы обеспечить корректный вызов конструктора базового класса.

Class LandLineSpyFoundException: ApplicationException { public LandLineSpyFoundException(string spyName) : base("шпийон"+spyName) { } public LandLineSpyFoundException(string spyName, Exception innerException) : base(spyName, innerException) { } }

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

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

В .NET Framework предусмотрена развитая система обработки ошибок. Механизм обработки ошибок C# позволяет закодировать пользовательскую обработку для каждого типа ошибочных условий, а также отделить код, потенциально порождающий ошибки, от кода, обрабатывающего их.

Что бы ни служило причиной проблем, в конечном итоге приложение начинает работать не так, как ожидается. Прежде чем переходить к рассмотрению структурированной обработки исключений, давайте сначала ознакомимся с тремя наиболее часто применяемыми для описания аномалий терминами:

Программные ошибки (bugs)

Так обычно называются ошибки, которые допускает программист. Например, предположим, что приложение создается с помощью неуправляемого языка С++. Если динамически выделяемая память не освобождается, что чревато утечкой памяти, появляется программная ошибка.

Пользовательские ошибки (user errors)

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

Исключения (exceptions)

Исключениями, или исключительными ситуациями, обычно называются аномалии, которые могут возникать во время выполнения и которые трудно, а порой и вообще невозможно, предусмотреть во время программирования приложения. К числу таких возможных исключений относятся попытки подключения к базе данных, которой больше не существует, попытки открытия поврежденного файла или попытки установки связи с машиной, которая в текущий момент находится в автономном режиме. В каждом из этих случаев программист (и конечный пользователь) мало что может сделать с подобными "исключительными" обстоятельствами.

По приведенным выше описаниям должно стать понятно, что структурированная обработка исключений в.NET представляет собой методику, предназначенную для работы с исключениями, которые могут возникать на этапе выполнения. Даже в случае программных и пользовательских ошибок, которые ускользнули от глаз программиста, однако, CLR будет часто автоматически генерировать соответствующее исключение с описанием текущей проблемы. В библиотеках базовых классов.NET определено множество различных исключений, таких как FormatException, IndexOutOfRangeException, FileNotFoundException, ArgumentOutOfRangeException и т.д.

В терминологии.NET под "исключением" подразумеваются программные ошибки, пользовательские ошибки и ошибки времени выполнения. Прежде чем погружаться в детали, давайте посмотрим, какую роль играет структурированная обработка исключений, и чем она отличается от традиционных методик обработки ошибок.

Роль обработки исключений в.NET

До появления.NET обработка ошибок в среде операционной системы Windows представляла собой весьма запутанную смесь технологий. Многие программисты включали собственную логику обработки ошибок в контекст интересующего приложения. Например, команда разработчиков могла определять набор числовых констант для представления известных сбойных ситуаций и затем применять эти константы в качестве возвращаемых значений методов.

Помимо приемов, изобретаемых самими разработчиками, в API-интерфейсе Windows определены сотни кодов ошибок с помощью #define и HRESULT, а также множество вариаций простых булевских значений (bool, BOOL, VARIANT BOOL и т.д.). Более того, многие разработчики СОМ-приложений на языке С++ (а также VB 6) явно или неявно применяют небольшой набор стандартных СОМ-интерфейсов (наподобие ISupportErrorlnfo. IErrorlnfo или ICreateErrorlnfо) для возврата СОМ-клиенту понятной информации об ошибках.

Очевидная проблема со всеми этими более старыми методиками - отсутствие симметрии. Каждая из них более-менее вписывается в рамки какой-то одной технологии, одного языка и, пожалуй, даже одного проекта. В.NET поддерживается стандартная методика для генерации и выявления ошибок в исполняющей среде, называемая структурированной обработкой исключений (SEH - structured exception handling) .

Прелесть этой методики состоит в том, что она позволяет разработчикам использовать в области обработки ошибок унифицированный подход, который является общим для всех языков, ориентированных на платформу.NET. Благодаря этому, программист на C# может обрабатывать ошибки почти таким же с синтаксической точки зрения образом, как и программист на VB и программист на С++, использующий C++/CLI.

Дополнительное преимущество состоит в том, что синтаксис, который требуется применять для генерации и перехвата исключений за пределами сборок и машин, тоже выглядит идентично. Например, при написании на C# службы Windows Communication Foundation (WCF) генерировать исключение SOAP для удаленного вызывающего кода можно с использованием тех же ключевых слов, которые применяются для генерации исключения внутри методов в одном и том же приложении.

Последнее обновление: 23.10.2018

Иногда при выполнении программы возникают ошибки, которые трудно предусмотреть или предвидеть, а иногда и вовсе невозможно. Например, при передачи файла по сети может неожиданно оборваться сетевое подключение. такие ситуации называются исключениями . Язык C# предоставляет разработчикам возможности для обработки таких ситуаций. Для этого в C# предназначена конструкция try...catch...finally .

Try { } catch { } finally { }

При использовании блока try...catch..finally вначале выполняются все инструкции в блоке try . Если в этом блоке не возникло исключений, то после его выполнения начинает выполняться блок finally . И затем конструкция try..catch..finally завершает свою работу.

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

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

Рассмотрим следующий пример:

Class Program { static void Main(string args) { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); Console.WriteLine("Конец программы"); Console.Read(); } }

В данном случае происходит деление числа на 0, что приведет к генерации исключения. И при запуске приложения в режиме отладки мы увидим в Visual Studio окошко, которое информирует об исключении:

В этом окошке мы видим, что возникло исключение, которое представляет тип System.DivideByZeroException , то есть попытка деления на ноль. С помощью пункта View Details можно посомтреть более детальную информацию об исключении.

И в этом случае единственное, что нам остается, это завершить выполнение программы.

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

Class Program { static void Main(string args) { try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); } finally { Console.WriteLine("Блок finally"); } Console.WriteLine("Конец программы"); Console.Read(); } }

В данном случае у нас опять же возникнет исключение в блоке try, так как мы пытаемся разделить на ноль. И дойдя до строки

Int y = x / 0;

выполнение программы остановится. CLR найдет блок catch и передаст управление этому блоку.

После блока catch будет выполняться блок finally.

Возникло исключение! Блок finally Конец программы

Таким образом, рограмма по-прежнему не будет выполнять деление на ноль и соответственно не будет выводить результат этого деления, но теперь она не будет аварийно завершаться, а исключение будет обрабатываться в блоке catch.

Следует отметить, что в этой конструкции обязателен блок try . При наличии блока catch мы можем опустить блок finally:

Try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); }

И, наоборот, при наличии блока finally мы можем опустить блок catch и не обрабатывать исключение:

Try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } finally { Console.WriteLine("Блок finally"); }

Однако, хотя с точки зрения синтаксиса C# такая конструкция вполне корректна, тем не менее, поскольку CLR не сможет найти нужный блок catch, то исключение не будет обработано, и программа аварийно завершится.

Обработка исключений и условные конструкции

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

Static void Main(string args) { Console.WriteLine("Введите число"); int x = Int32.Parse(Console.ReadLine()); x *= x; Console.WriteLine("Квадрат числа: " + x); Console.Read(); }

Если пользователь введет не число, а строку, какие-то другие символы, то программа выпадет в ошибку. С одной стороны, здесь как раз та ситуация, когда можно применить блок try..catch , чтобы обработать возможную ошибку. Однако гораздо оптимальнее было бы проверить допустимость преобразования:

Static void Main(string args) { Console.WriteLine("Введите число"); int x; string input = Console.ReadLine(); if (Int32.TryParse(input, out x)) { x *= x; Console.WriteLine("Квадрат числа: " + x); } else { Console.WriteLine("Некорректный ввод"); } Console.Read(); }

Метод Int32.TryParse() возвращает true , если преобразование можно осуществить, и false - если нельзя. При допустимости преобразования переменная x будет содержать введенное число. Так, не используя try...catch можно обработать возможную исключительную ситуацию.

С точки зрения производительности использование блоков try..catch более накладно, чем применение условных конструкций. Поэтому по возможности вместо try..catch лучше использовать условные конструкции на проверку исключительных ситуаций.

Перехват исключений

Принимая во внимание, что.NET Framework включает большое количество предопределенных классов исключений, возникает вопрос: как их использовать в коде для перехвата ошибочных условий? Для того чтобы справиться с возможными ошибочными ситуациями в коде C#, программа обычно делится на блоки трех разных типов:

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

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

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

Try и catch

Основу обработки исключительных ситуаций в C# составляет пара ключевых слов try и catch. Эти ключевые слова действуют совместно и не могут быть использованы порознь. Ниже приведена общая форма определения блоков try/catch для обработки исключительных ситуаций:

try { // Блок кода, проверяемый на наличие ошибок. } catch (ExcepType1 exOb) { // Обработчик исключения типа ExcepType1. } catch (ExcepType2 exOb) { // Обработчик исключения типа ExcepType2. } ...

где ExcepType - это тип возникающей исключительной ситуации. Когда исключение генерируется оператором try, оно перехватывается составляющим ему пару оператором catch, который затем обрабатывает это исключение. В зависимости от типа исключения выполняется и соответствующий оператор catch. Так, если типы генерируемого исключения и того, что указывается в операторе catch, совпадают, то выполняется именно этот оператор, а все остальные пропускаются. Когда исключение перехватывается, переменная исключения exOb получает свое значение. На самом деле указывать переменную exOb необязательно. Так, ее необязательно указывать, если обработчику исключений не требуется доступ к объекту исключения, что бывает довольно часто. Для обработки исключения достаточно и его типа.

Следует, однако, иметь в виду, что если исключение не генерируется, то блок оператора try завершается как обычно, и все его операторы catch пропускаются. Выполнение программы возобновляется с первого оператора, следующего после завершающего оператора catch. Таким образом, оператор catch выполняется лишь в том случае, если генерируется исключение.

Давайте рассмотрим пример, в котором будем обрабатывать исключение, возникающее при делении числа на 0:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static int MyDel(int x, int y) { return x / y; } static void Main() { try { Console.Write("Введите x: "); int x = int.Parse(Console.ReadLine()); Console.Write("Введите y: "); int y = int.Parse(Console.ReadLine()); int result = MyDel(x, y); Console.WriteLine("Результат: " + result); } // Обрабатываем исключение возникающее при делении на ноль catch (DivideByZeroException) { Console.WriteLine("Деление на 0 detected!!!\n"); Main(); } // Обрабатываем исключение при неккоректном вводе числа в консоль catch (FormatException) { Console.WriteLine("Это НЕ число!!!\n"); Main(); } Console.ReadLine(); } } }

Данный простой пример наглядно иллюстрирует обработку исключительной ситуации при делении на 0 (DivideByZeroException), а так же пользовательскую ошибку при вводе не числа (FormatException).

Последствия неперехвата исключений

Перехват одного из стандартных исключений, как в приведенном выше примере, дает еще одно преимущество: он исключает аварийное завершение программы. Как только исключение будет сгенерировано, оно должно быть перехвачено каким-то фрагментом кода в определенном месте программы. Вообще говоря, если исключение не перехватывается в программе, то оно будет перехвачено исполняющей системой. Но дело в том, что исполняющая система выдаст сообщение об ошибке и прервет выполнение программы.