Вернемся к рассмотрению метода класса Person, который обеспечивал некоторые ограничения рассматриваемой предметной области.
public void SetHeight (double newHeight)
{ if ((newHeight>0)&&( newHeight<230)&& (newHeight>height))
height=newHeight;
}
Использование этого метода с некорректными данными никак не влияет на состояние объекта Person (у if-оператора нет else-части). Как ни странно, это приводит к еще худшим последствиям. Предположим, что программа выполнила оператор p.SetHeight(-10). После выполнения метода объект остается в прежнем состоянии, и программа продолжает «корректно» работать. Теперь трудно будет обнаружить, почему дальнейшее использование такого объекта приводит к ошибочным последствиям.
При отсутствии контроля дальнейшее использование объекта человек, скорее всего привело бы к аварийному завершению программы (например, попытка вычислить квадратный корень высоты в более точных формулах определения полноты человека).
Попробуем улучшить реализацию метода SetHeight, разместив в части else оператор вывода диагностического сообщения:
public void SetHeight (newHeight)
{ if (newHeight>0)&&( newHeight<230)&& (newHeight>height)
Это не улучшает ситуацию. Во-первых, при использовании в рамках консольного приложения, пользователь программы может просто не заметить дополнительной строчки, выводимой на экран – программа «успешно» продолжает работать и выводить другую информацию. Во-вторых, такой класс Person нельзя использовать в оконных приложениях, где нельзя использовать класс Console.
Все это означает, что в else-части нужно осуществлять завершение программы. Однако в C# нет оператора, позволяющего это сделать в любом месте программного кода. И это не случайный недостаток языка.
В этой ситуации уместно использовать ряд возможностей языка C#, известных как средства обработки исключительных ситуаций. Термин «исключительная ситуация» можно считать синонимом понятия «ошибка» со следующей оговоркой – кроме стандартных ошибок (деление на ноль, обращение к несуществующему файлу и т.д.) исключительные ситуации могут описывать боле широкий круг обстоятельств, которые программист считает ошибочными. Таким образом, речь идет о возможности определять свои собственные исключительные ситуации.
В .NET имеется стандартный класс Exception, который представляет объект, содержащий информацию о возникшей в ходе выполнения программы, ошибке. Этот объект для стандартных ошибок создается автоматически. Если же Вам нужно создать собственную исключительную ситуацию, то простейшим способом это сделать будет использование оператора следующего вида:
throw new Exception(“строка с описанием ошибки”);
Здесь с помощью конструктора класса Exception создается объект-ошибка, хранящий указанное параметром строковое описание. В дальнейшем к этой строке можно получить доступ с помощью свойства Message класса Exception. Далее, оператор throw «активизирует» этот объект, что обычно приводит к аварийному завершению программы с выдачей указанного сообщения в стандартном окне, формируемом операционной системой. Таким образом, новая версия метода SetHeight выглядит следующим образом:
public void SetHeight (double newHeight)
{ if ((newHeight>0)&&( newHeight<230)&& (newHeight>height))
height=newHeight;
else throw new Exception(“недопустимая высота”);
}
Пока что наша программа сумела сгенерировать специфическую для класса Person ошибку. Будет еще лучше, если мы научимся обрабатывать такую ошибку. Под обработкой ошибки не следует понимать полную нейтрализацию ошибочной ситуации с выводом программы в нормальный режим работы. Нельзя назвать эвакуацию населения в большом населенном пункте выходом в нормальный режим. Обработка ошибок – это сравнительно небольшие программные действия по ликвидации последствий аварийного завершения программы. Будет сделана попытка предпринять эти действия непосредственно в момент возникновения ошибки, но избежать аварийного, с точки зрения операционной системы, выхода из программы. Программа продолжит свое выполнение с «минимальными» потерям.
Продемонстрируем эти новые языковые возможности на примере метода Main, использующего объекты класса Person:
static void Main(string[] args)
{ Person p = new Person();
double age = Convert.ToDouble(Console.ReadLine());
try
{ p.SetHeight(age); }
catch
{ Console.WriteLine("Неверный рост"); }
}
Обратите внимание на появление конструкции
try
{ блок операторов }
catch
{ блок операторов }
Каждый из двух новых блоков (try и catch) может состоять из произвольного количества операторов и формирует самостоятельную область видимости переменных.
Если при выполнении операторов try-блока происходит ошибка, дальнейшее выполнение передается catch-блок. Обратите внимание, что ошибка может произойти в любом месте try-блока. Это может быть встроенная ошибочная ситуация (например, деление на 0) или ошибка, реализованная программистом с помощью оператора throw. В нашем случае ошибка произойдет, если пользователь введет некорректное значение возраста (например, отрицательное число). Если же пользователь введет строку, не являющуюся изображением числа, то программа не сумеет обработать эту ошибку. Дело в том, что такая ошибка происходит при выполнении метода Convert.ToDouble, которое в примере происходит за пределами try-блока. Поэтому переместим соответстующий оператор в try-блок:
try
{ double h = Convert.ToDouble(Console.ReadLine());
p.SetHeight(h);
}
catch
{ Console.WriteLine("Неверный рост"); }
Ситуация улучшилась. Но теперь становится актуальной другая проблема – в программе возникают ошибки различных типов. Однако их обработка выполняется одинаково. В нашем случае – выдачей сообщения "Неверный рост". С этим можно справиться, используя параметр в заголовке catch-блока:
try
{ double h = Convert.ToDouble(Console.ReadLine());
p.SetHeight(h);
}
catch (Exception e)
{ if(e.Message=="недопустимый рост")
Console.WriteLine("Неверный рост");
else
Console.WriteLine("Другая ошибка");
}
Теперь возникающий во время выполнения объект-ошибка как фактический параметр передается в формальный параметр e блока catch. В классе Exception имеется свойство Message, значение которого для объекта-ошибки определяется в момент его создания. В нашем случае мы создаем объект-ошибку со значением Message равным "недопустимый рост". Благодаря этому в catch-блоке удается распознать тип ошибки и правильно на нее отреагировать.