Студопедия

КАТЕГОРИИ:

АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция

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




Лекция 9

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

Базовый класс для массивов

В языке C# массивы, как и типы данных, являются классами. Если объявить, например, целочисленный массив int[], то он автоматически наследуется от класса Array из пространства имён System. В классе Array определены несколько важных статических методов:

1. IndexOf() ‒ определяет индекс элемента в массиве. Если объект в массиве не найден, то метод вернет индекс наименьшего элемента в массиве минус единица. Индексы нумеруются с нуля, поэтому результатом будет -1;

2. Clear() ‒ этот метод предоставляет возможность удалить из массива определенное количество элементов. Например, чтобы из массива myArray удалить 5 элементов, начиная со 2-го, нужно записать:

Array.Clear(myArray, 2, 5);

3. СоруТо() ‒ позволяет скопировать данные одного массива в другой;

4. LastIndexOf() ‒ находит последний индекс объекта в массиве, если в массив несколько раз добавлен указанный объект;

5. Reverse() ‒ переворачивает массив в обратном направлении;

6. Sort() ‒ сортирует массив.

Класс также содержит свойство Length, которое позволяет определить количество элементов массива.

Пример использования некоторых методов и свойств класса:

int[] test = {10, 20, 1, 6, 15 };

// сортировка

Console.WriteLine("Отсортированная версия: ");

Array.Sort(test);

foreach (int i in test)

{

   Console.WriteLine(i);

}

// реверс элементов массива

Console.WriteLine("Реверсная версия: ");

Array.Reverse(test);

foreach (int i in test)

{

    Console.WriteLine(i);

}

// Удаление двух элементов массива

Console.WriteLine("Текущий размер: {0}", test.Length);

Array.Clear(test, 2, 2);

Console.WriteLine("После удаления: {0}", test.Length);

foreach (int i in test)

{

   Console.WriteLine(i);

}

Невыровненные массивы

До сих пор речь шла о ровных многомерных массивах. Если объявить двумерный массив в виде таблицы, то в каждой строке такого массива ровно столько элементов, сколько указано при создании, и это количество неизменно. Допустим, необходимо создать двумерный массив, у которого в первой строке 1 элемент, во второй строке 10 элементов, в третьей 5 и т. д. Такие массивы называются невыровненными.

Невыровненный двумерный массив объявляется в виде:

Тип_данных[] [] переменная;

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

int [][] jaggedArray = new int[10] [];

jaggedArray[1] = new int[5];

jaggedArray[2] = new int[2];

jaggedArray[4] = new int[20];

В данном примере объявлена переменная jaggedArray, состоящая из 10 строк. После этого для отдельных строк задается явно количество элементов в строке. Количество элементов задано только для строк с индексами 1, 2 и 4. Остальные останутся нулевыми (не проинициализированными), и доступ к ним останется запрещенным. При попытке прочитать или изменить значения элементов в этих строках произойдёт ошибка.

При обращении к элементам такого массива каждую размерность нужно указывать в отдельной паре квадратных скобок:

jaggedArray[1][2] = 1;

Ещё один пример работы с невыровненным массивом:

int[][] jaggedArray = new int[10][];

// массив выделения памяти для каждой строки

for (int i = 0; i < jaggedArray.Length; i++)

{

     jaggedArray[i] = new int[i];

}

// использование массива

for (int i = 0; i < jaggedArray.Length; i++)

{

   for (int j = 0; j < jaggedArray[i].Length; j++)

    {

      jaggedArray[i][j] = j;

   }

Console.Write(jaggedArray[i][j]);

}

Console.WriteLine();

}

Чтобы создать треугольный массив, после объявления невыровненного массива запускается цикл, который выполняется от нуля до количества элементов в массиве. Внутри цикла инициализируем очередную строку текущим значением счетчика в переменной i. Другие два цикла перебирают все строки массива и элементы в них. Значения этих элементов выводятся в консоль.

Динамические массивы

Все массивы, о которых ранее шла речь, обладают одним серьёзным недостатком – их размер фиксирован и определяется во время инициализации. Но в реальной жизни далеко не всегда известно, какого размера должен быть массив во время выполнения программы. Можно попытаться предсказать максимально возможный результат и выделить для его сохранения максимально возможное количество данных, но при этом тратится слишком много памяти и нет гарантии, что учтён действительно самый «неблагоприятный» вариант. Например, при написании программы, которая хранит количество машин на автостоянке, можно на всякий случай выделить место для 50 элементов в массиве. Но если на стоянке будет только одна машина, память для 49 элементов будет расходоваться впустую. В то же время, если стоянка вдвое расширится и сможет вмещать 100 машин, при добавлении 51-й машины в массив произойдёт ошибка – он будет переполнен.

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

Пусть необходимо наделить класс Person (человек) возможностью хранения списка детей. Возможный вариант решения приведён ниже:

…………………………………………………………………

 ArrayList Children = new ArrayList();

public void AddChild(string firstName, string lastName)

{

     Children.Add(new Person(firstName, lastName));

}

public void DeleteChild(int index)

{

     Children.RemoveAt(index);

  }

public Person GetChild(int index)

{

     return (Person)Children[index];

}

…………………………………………………………………

Наиболее интересные методы и свойства класса ArrayList:

1. Count ‒ свойство, которое позволяет узнать количество элементов в массиве;

2. Add() ‒ добавление элемента, переданного в качестве параметра, в список;

3. Remove() ‒ удаление элемента, объект которого указан в качестве параметра. Если такой объект не найден в списке, то удаления не произойдет;

4. RemoveAt() ‒ удаление элемента с индексом, переданного в качестве параметра;

5. Clear() ‒ удаление содержимого списка.

Класс ArrayList также наследует все свойства и методы базового класса Array.

   

Интерфейсы массивов

Существуют несколько интерфейсов, которые позволяют по-разному работать с массивами. Несколько самых важных из них:

1. ICollection — коллекция определяет методы добавления элементов в массив, получения интерфейса перечисления элементов и определения количества элементов. Именно этот интерфейс используется для доступа к элементам массива таких компонентов, как ListView, ListBox и т. д;

2. IComparer – используется для сравнения элементов во время сортировки;

3. IDictionary – интерфейс, позволяющий реализовать доступ к элементам по ключу/значению.

4. IEnumerable — если класс реализует этот интерфейс, то объект этого класса можно использовать в операторе цикла foreach, т. е. он содержит необходимые методы, через которые можно перебирать элементы списка;

5. IList – если класс реализует этот интерфейс, то к элементам его массива можно обращаться по индексу.

 

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

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

Предположим, в некотором классе определён метод MyMul, выполняющий деление двух чисел:

double MyMul(int x, int y)

{

     return x / y;

}

«Рискованным» моментом в коде является деление. Если значение параметра y будет равно нулю, при попытке деления компьютер сгенерирует ошибку.

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

Категории ошибок, которые могут вызывать исключительные ситуации:

1. Ошибки при работе с ресурсами компьютера, необходимыми программе.

2. Ошибки логики приложений.

3. Пользовательские ошибки.

Исключения в C# строятся на основе ключевых слов try, catch, throw и finally, а также на основе классов исключительных ситуаций. Базовый класс для исключений – Exception из пространства имён System.

Самые интересные свойства/методы в классе Exception:

1) InnerException – информация о внутренних исключениях, которые стали причиной данной исключительной ситуации;

2) Message – короткое текстовое описание ошибки;

3) Source – имя сборки, сгенерировавшей исключение

4) StackTrace – строка, содержащая последовательность вызовов, которые привели к ошибке. Это свойство может быть полезно с точки зрения отладки кода и возникшей проблемы.

Рассмотрим обработку исключений на примере классической задачи превращения строки в число. Предположим, внутри метода Main() написан следующий код:

while (true)

 {

      Console.WriteLine("Введите число");

      string inLine = Console.ReadLine();

      if (inLine == "q")

      {

           break;

      }

      int i = Convert.ToInt32(inLine);

      Console.WriteLine("Вы ввели {0}", i);

}

Этот цикл будет выполняться бесконечно, так как условие в скобках постоянно равно true. Единственный способ его прервать – написать оператор break внутри цикла. Если введена буква q, работа цикла завершается, иначе происходит превращение строки в число. Всё будет работать прекрасно, пока не будет введено слишком большое число, которое не может быть преобразовано в тип данных Int32, либо какая-то другая буква, кроме q. В таком случае программа завершит работу аварийно, а на экране появится окно с сообщением об ошибке.

 

 

В данном случае самый приемлемый подход – применение механизма исключительных ситуаций. Самый простой способ «отловить» проблемный код – заключить его в блок try:

try

{

     int i = Convert.ToInt32(inLine);

     Console.WriteLine("Вы ввели {0}", i);

}

catch (Exception fe)

{

    Console.WriteLine(fe.Message);

}

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

try

{

   int i = Convert.ToInt32(inLine);

   Console.WriteLine("Вы ввели {0}", i);

}

catch (FormatException)

{

    Console.WriteLine("Вы ввели некорректное число {0}", inLine);

}

catch (Exception e)

{

   Console.WriteLine(e.Message);

}

После ключевого слова catch в скобках указывается имя класса исключительной ситуации, который необходимо «отловить». В первом случае catch будет обрабатывать события класса FormatException, т. е. связанные с некорректным форматом данных. Во втором блоке catch стоит класс Exception, который является базовым для всех, следовательно, он отловит любое исключение, которое не было отловлено в предыдущих блоках. В данном примере в этом блоке выводится свойство Message, где находится описание ошибки.

В блоки try нужно стараться включать только действительно необходимый код. Пример неудачного оформления блока:

while (true)

{

    try

    {

         Console.WriteLine("Введите число");

         string inLine = Console.ReadLine();

         if (inLine == "q")

         {

             break;

         }

         int i = Convert.ToInt32(inLine);

         Console.WriteLine("Вы ввели {0}", i);

      }

      catch (Exception e)

      {

      }

    }

Код в этом примере «плохой», потому что здесь в блок try заключено все содержимое цикла. В блоке catch ловятся все события благодаря использованию класса Exception, и ничего не написано, нет никакой реакции на исключительную ситуацию. Такой подход является попыткой заглушить исключительную ситуацию без попытки локализовать проблему.

Исключительные ситуации создаются не только системой. В C# предусмотрена возможность самостоятельного создания исключительной ситуации, и для этого используется ключевое слово throw.

Допустим, необходимо, чтобы вводимое число было не более 10. С помощью исключительной ситуации проверку можно сделать так:

if (index > 10)

{

throw new Exception("Вы ввели слишком большое значение");

}

Ключевое слово throw генерирует исключение, объект которого мы создаем после указания этого слова. В данном простейшем примере создаётся экземпляр базового класса Exception. В качестве параметра конструктору класса передаётся описание ошибки, которое попадет в свойство Message созданного объекта исключительной ситуации.

В .NET существует иерархия классов исключительных ситуаций. Основные ветки:

SystemException – исключительные ситуации этого класса и его подклассов генерируются общеязыковой средой выполнения CLR и являются исключениями системного уровня. Такие ошибки считаются неустранимыми;

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

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

Быстрый способ узнать, какие классы исключений может сгенерировать метод – поставить курсор ввода на нужный метод и нажать комбинацию клавиш <Ctrl>+<K>, <I> или просто навести на метод указатель мыши. Должно появиться небольшое окно с кратким описанием метода и со списком возможных исключительных ситуаций во время вызова метода. Пример такого окна приведён ниже:

 

 

 

 


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

try

{

   // код

}

catch

{

    // произошла ошибка в коде

}

finally

{

    // код выполнится вне зависимости от наличия исключения

}

Этот блок удобно использовать при работе с какими-то выделяемыми ресурсами, например, файлами:

try

{

    ОткрытьФайл;

    Прочитать данные из файла;

    Обработать данные;

}

catch

{

    Сообщить пользователю об ошибке при работе с файлом;

}

finally

{

    if (файл открыт)

       Закрыть файл;

}

В блоке try заключён метод открытия файла, который может сгенерировать исключение. Так как вызов закрытия файла написан в блоке finally, который выполняется вне зависимости от наличия исключения, то этим гарантируется, что файл будет закрыт в любом случае ‒ корректно отработали с ним или нет.










Последнее изменение этой страницы: 2018-05-10; просмотров: 178.

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