Текст книги "Полное руководство. С# 4.0"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 26 (всего у книги 58 страниц)
ГЛАВА 14. Применение средств ввода-вывода
В примерах программ, приводившихся в предыду щих главах, уже применялись отдельные части си стемы ввода-вывода в С#, например метод Console. WriteLine(), но делалось это без каких-либо формальных пояснений. Система ввода-вывода основана в C# на иерар хии классов, поэтому ее функции и особенности нельзя было представлять до тех пор, пока не были рассмотрены классы, наследование и исключения. А теперь настал черед и для ввода-вывода. В C# применяется система ввода-вывода и классы, определенные в среде .NET Framework, и поэтому рассмотрение ввода-вывода в этом языке относится ко всей системе ввода-вывода среды .NET в целом.
В этой главе речь пойдет о средствах консольного и фай лового ввода-вывода. Следует, однако, сразу же предупре дить, что система ввода-вывода в C# довольно обширна. По этому в этой главе рассматриваются лишь самые важные и наиболее часто используемые ее средства. Организация системы ввода-вывода в C# на потоках
Ввод-вывод в программах на C# осуществляется посред ством потоков. Поток – это некая абстракция производ ства или потребления информации. С физическим устрой ством поток связывает система ввода-вывода. Все потоки действуют одинаково – даже если они связаны с разными физическими устройствами. Поэтому классы и методы ввода-вывода могут применяться к самым разным типам устройств. Например, методами вывода на консоль можно пользоваться и для вывода в файл на диске. Байтовые и символьные потоки
На самом низком уровне ввод-вывод в С# осуществляется байтами. И делается это потому, что многие устройства ориентированы на операции ввода-вывода отдельными байтами. Но человеку больше свойственно общаться символами. Напомним, что в C# тип char является 16-разрядным, а тип byte – 8-разрядным. Так, если в целях ввода– вывода используется набор символов в коде ASCII, то для преобразования типа char в тип byte достаточно отбросить старший байт значения типа char. Но это не годится для набора символов в уникоде (Unicode), где символы требуется представлять двумя, а то и больше байтами. Следовательно, байтовые потоки не совсем подходят для ор ганизации ввода-вывода отдельными символами. С целью разрешить это затруднение в среде .NET Framework определено несколько классов, выполняющих превращение байтового потока в символьный с автоматическим преобразованием типа byte в тип char и обратно. Встроенные потоки
Для всех программ, в которых используется пространство имен System, доступны встроенные потоки, открывающиеся с помощью свойств Console.In, Console.Out и Console.Error. В частности, свойство Console.Out связано со стандартным по током вывода. По умолчанию это поток вывода на консоль. Так, если вызывается метод Console.WriteLine(), информация автоматически передается свойству Console. Out. Свойство Console.In связано со стандартным потоком ввода, который по умол чанию осуществляется с клавиатуры. А свойство Console.Error связано со стан дартным потоком сообщений об ошибках, которые по умолчанию также выводятся на консоль. Но эти потоки могут быть переадресованы на любое другое совместимое устройство ввода-вывода. Стандартные потоки являются символьными. Поэтому в эти потоки выводятся и вводятся из них символы. Классы потоков
В среде .NET Framework определены классы как для байтовых, так и для символь ных потоков. Но на самом деле классы символьных потоков служат лишь оболочками для превращения заключенного в них байтового потока в символьный, автоматически выполняя любые требующиеся преобразования типов данных. Следовательно, сим вольные потоки основываются на байтовых, хотя они и разделены логически. Основные классы потоков определены в пространстве имен System.IO. Для того чтобы воспользоваться этими классами, как правило, достаточно ввести приведенный ниже оператор в самом начале программы. using System.IO;
Пространство имен System.IO не указывается для консольного ввода-вывода по тому, что для него определен класс Console в пространстве имен System. Класс Stream
Основным для потоков является класс System.IO.Stream. Он представляет бай товый поток и является базовым для всех остальных классов потоков. Кроме того, он является абстрактным классом, а это означает, что получить экземпляр объекта класса Stream нельзя. В классе Stream определяется ряд операций со стандартными пото ками, представленных соответствующими методами. В табл. 14.1 перечислен ряд наи более часто используемых методов, определенных в классе Stream.
Таблица 14.1. Некоторые методы, определенные в классе Stream Метод Описание void Close() Закрывает поток void Flush() Выводит содержимое потока на физическое устройство int ReadByte() Возвращает целочисленное представление следующего байта, доступного для ввода из потока. При обнаружении конца файла возвращает значение -1 int Read(byte[] buffer, int offset, int count) Делает попытку ввести count байтов в массив buffer, начиная с элемента buffer[offset]. Возвращает количество успешно введенных байтов long Seek(long offset, SeekOrigin origin) Устанавливает текущее положение в потоке по указанному смещению offset относительно заданного начала отсчета origin. Возвращает новое положение в потоке void WriteByte(byte value) Выводит один байт в поток вывода void Write(byte[]buffer, int offset, int count) Выводит подмножество count байтов из массива buffer, начиная с элемента buffer[offset]. Возвращает количество выведенных байтов
Некоторые из методов, перечисленных в табл. 14.1, генерируют исключение IOException при появлении ошибки ввода-вывода. Если же предпринимается по пытка выполнить неверную операцию, например вывести данные в поток, предназна ченный только для чтения, то генерируется исключение NotSupportedException. Кроме того, могут быть сгенерированы и другие исключения – все зависит от кон кретного метода.
Следует заметить, что в классе Stream определены методы для ввода (или чтения) и вывода (или записи) данных. Но не все потоки поддерживают обе эти операции, поскольку поток можно открывать только для чтения или только для записи. Кроме того, не все потоки поддерживают запрос текущего положения в потоке с помощью метода Seek(). Для того чтобы определить возможности потока, придется воспользо ваться одним, а то и несколькими свойствами класса Stream. Эти свойства перечисле ны в табл. 14.2 наряду со свойствами Length и Position, содержащими длину потока и текущее положение в нем.
Таблица 14.2. Свойства, определенные в классе Stream Свойство Описание bool CanRead Принимает значение true, если из потока можно ввести данные. Доступно только для чтения bool CanSeek Принимает значение true, если поток поддерживает запрос текущего положения в потоке. Доступно только для чтения bool CanWrite Принимает значение true, если в поток можно вывести данные. Доступно только для чтения long Length Содержит длину потока. Доступно только для чтения long Position Представляет текущее положение в потоке. Доступно как для чтения, так и для записи int ReadTimeout Представляет продолжительность времени ожидания в операциях ввода. Доступно как для чтения, так и для записи int WriteTimeout Представляет продолжительность времени ожидания в операциях вывода. Доступно как для чтения, так и для записи Классы байтовых потоков
Производными от класса Stream являются несколько конкретных классов байтовых потоков. Эти классы определены в пространстве имен System.IO и перечислены ниже. Класс потока Описание BufferedStream Заключает в оболочку байтовый поток и добавляет буферизацию. Буферизация, как правило, повышает производительность FileStream Байтовый поток, предназначенный для файлового ввода-вывода MemoryStream Байтовый поток, использующий память для хранения данных UnmanagedMemoryStream Байтовый поток, использующий неуправляемую память для хранения данных
В среде NET Framework поддерживается также ряд других конкретных классов по токов, в том числе для ввода-вывода в сжатые файлы, сокеты и каналы. Кроме того, можно создать свои собственные производные классы потоков, хотя для подавляющего числа приложений достаточно и встроенных потоков. Классы-оболочки символьных потоков
Для создания символьного потока достаточно заключить байтовый поток в один из классов-оболочек символьных потоков. На вершине иерархии классов символь ных потоков находятся абстрактные классы TextReader и TextWriter. Так, класс TextReader организует ввод, а класс TextWriter – вывод. Методы, определенные в обоих этих классах, доступны для всех их подклассов. Они образуют минимальный набор функций ввода-вывода, которыми должны обладать все символьные потоки.
В табл. 14.3 перечислены методы ввода, определенные в классе TextReader. В це лом, эти методы способны генерировать исключение IOException при появлении ошибки ввода, а некоторые из них – исключения других типов. Особый интерес вы зывает метод ReadLine(), предназначенный для ввода целой текстовой строки, воз вращая ее в виде объекта типа string. Этот метод удобен для чтения входных данных, содержащих пробелы. В классе TextReader имеется также метод Close(), опреде ляемый следующим образом. void Close()
Этот метод закрывает считывающий поток и освобождает его ресурсы.
Таблица 14.3. Методы ввода, определенные в классе TextReader Метод Описание int Рeек() Получает следующий символ из потока ввода, но не удаляет его. Возвращает значение -1, если ни один из символов не доступен int Read() Возвращает целочисленное представление следующего доступного символа из вызывающего потока ввода. При обнаружении конца потока возвращает значение -1 int Read(char[]buffer, int index, int count) Делает попытку ввести количество count символов в массив buffer, начиная с элемента buffer[index], и возвращает количество успешно введенных символов int ReadBlock(char[]buffer, int index, int count) Делает попытку ввести количество count сим волов в массив buffer, начиная с элемента buffer[index], и возвращает количество успешно введенных символов string ReadLine() Вводит следующую текстовую строку и возвращает ее в виде объекта типа string. При попытке прочитать признак конца файла возвращает пустое значение string ReadToEnd() Вводит все символы, оставшиеся в потоке, и возвращает их в виде объекта типа string
В классе TextWriter определены также варианты методов Write() и WriteLine(), предназначенные для вывода данных всех встроенных типов. Ниже в качестве при мера перечислены лишь некоторые из перегружаемых вариантов этих методов. Метод Описание void Write(int value) Выводит значение типа int void Write(double value) Выводит значение типа double void Write(bool value) Выводит значение типа bool void WriteLine(string value) Выводит значение типа string с последующим символом новой строки void WriteLine(uint value) Выводит значение типа uint с последующим символом новой строки void WriteLine(char value) Выводит символ с последующим символом новой строки
Все эти методы генерируют исключение IOException при появлении ошибки вывода.
Кроме того, в классе TextWriter определены методы Close() и Flush(), при веденные ниже. virtual void Close() virtual void Flush()
Метод Flush() организует вывод в физическую среду всех данных, оставшихся в выходном буфере. А метод Close() закрывает записывающий поток и освобождает его ресурсы.
Классы TextReader и TextWriter реализуются несколькими классами символь ных потоков, включая и те, что перечислены ниже. Следовательно, в этих классах потоков предоставляются методы и свойства, определенные в классах TextReader и TextWriter. Класс потока Описание StreamReader Предназначен для ввода символов из байтового потока. Этот класс является оболочкой для байтового потока ввода StreamWriter Предназначен для вывода символов в байтовый поток. Этот класс является оболочкой для байтового потока вывода StringReader Предназначен для ввода символов из символьной строки StringWriter Предназначен для вывода символов в символьную строку Двоичные потоки
Помимо классов байтовых и символьных потоков, имеются еще два класса двоич ных потоков, которые могут служить для непосредственного ввода и вывода двоичных данных – BinaryReader и BinaryWriter. Подробнее о них речь пойдет далее в этой главе, когда дойдет черед до файлового ввода-вывода.
А теперь, когда представлена общая структура системы ввода-вывода в С#, отведем оставшуюся часть этой главы более подробному рассмотрению различных частей дан ной системы, начиная с консольного ввода-вывода. Консольный ввод-вывод
Консольный ввод-вывод осуществляется с помощью стандартных потоков, представ ленных свойствами Console.In, Console.Out и Console.Error. Примеры консольною ввода-вывода были представлены еще в главе 2, поэтому он должен быть вам уже знаком. Как будет показано ниже, он обладает и рядом других дополнительных возможностей.
Но прежде следует еще раз подчеркнуть, что большинство реальных приложений C# ориентированы не на консольный ввод-вывод в текстовом виде, а на графический оконный интерфейс для взаимодействия с пользователем, или же они представляют собой программный код, используемый на стороне сервера. Поэтому часть системы ввода-вывода, связанная с консолью, не находит широкого практического применения. И хотя программы, ориентированные на текстовый ввод-вывод, отлично подходят в качестве учебных примеров, коротких сервисных программ или определенного рода программных компонентов, для большинства реальных приложений они не годятся. Чтение данных из потока ввода с консоли
Поток Console.In является экземпляром объекта класса TextReader, и поэто му для доступа к нему могут быть использованы методы и свойства, определенные в классе TextReader. Но для этой цеди чаще все же используются методы, предостав ляемые классом Console, в котором автоматически организуется чтение данных из по тока Console.In. В классе Console определены три метода ввода. Два первых метода, Read() и ReadLine(), были доступны еще в версии .NET Framework 1.0. А третий метод, ReadKey(), был добавлен в версию 2.0 этой среды.
Для чтения одного символа служит приведенный ниже метод Read(). static int Read()
Метод Read() возвращает очередной символ, считанный с консоли. Он ожидает до тех пор, пока пользователь не нажмет клавишу, а затем возвращает результат. Воз вращаемый символ относится к типу int и поэтому должен быть приведен к типу char. Если при вводе возникает ошибка, то метод Read() возвращает значение -1. Этот метод сгенерирует исключение IOException при неудачном исходе операции ввода. Ввод с консоли с помощью метода Read() буферизуется построчно, поэтому пользователь должен нажать клавишу , прежде чем программа получит любой символ, введенный с консоли.
Ниже приведен пример программы, в которой метод Read() используется для считывания символа, введенного с клавиатуры. // Считать символ, введенный с клавиатуры. using System; class KbIn { static void Main() { char ch; Console.Write("Нажмите клавишу, а затем –
Вот, например, к какому результату может привести выполнение этой программы. Нажмите клавишу, а затем –
Необходимость буферизировать построчно ввод, осуществляемый с консоли по средством метода Read(), иногда может быть досадным препятствием. Ведь при нажа тии клавиши в поток ввода передается последовательность символов перевода каретки и перевода строки. Более того, эти символы остаются во входном буфере до тех пор, пока они не будут считаны. Следовательно, в некоторых приложениях приходится удалять эти символы (путем их считывания), прежде чем приступать к следующей опе рации ввода. Впрочем, для чтения введенных с клавиатуры символов без построчной буферизации, можно воспользоваться рассматриваемым далее методом ReadKey().
Для считывания строки символов служит приведенный ниже метод ReadLine(). static string ReadLine()
Символы считываются методом ReadLine() до тех пор, пока пользователь не нажмет клавишу
Ниже приведен пример программы, в которой демонстрируется чтение строки из потока Console.In с помощью метода ReadLine(). // Ввод с консоли с помощью метода ReadLine(). using System; class ReadString { static void Main() { string str; Console.WriteLine("Введите несколько символов."); str = Console.ReadLine(); Console.WriteLine("Вы ввели: " + str); } }
Выполнение этой программы может привести, например, к следующему результату. Введите несколько символов. Это просто тест. Вы ввели: Это просто тест.
Итак, для чтения данных из потока Console.In проще всего воспользоваться мето дами класса Console. Но для этой цели можно обратиться и к методам базового клас са TextReader. В качестве примера ниже приведен переделанный вариант предыду щего примера программы, в котором используется метод Rea.dLine(), определенный в классе TextReader. // Прочитать введенную с клавиатуры строку // непосредственно из потока Console.In. using System; class ReadChars2 { static void Main() { string str; Console.WriteLine("Введите несколько символов."); str = Console.In.ReadLine(); // вызвать метод ReadLine() класса TextReader Console.WriteLine("Вы ввели: " + str); } }
Обратите внимание на то, что метод ReadLine() теперь вызывается непосредствен но для потока Console.In. Поэтому если требуется доступ к методам, определенным в классе TextReader, который является базовым для потока Console.In, то подобные методы вызываются так, как было показано в приведенном выше примере. Применение метода ReadKey()
В состав среды .NET Framework включен метод, определяемый в классе Console и позволяющий непосредственно считывать отдельно введенные с клавиатуры символы без построчной буферизации. Этот метод называется ReadKey(). При нажа тии клавиши метод ReadKey() немедленно возвращает введенный с клавиатуры сим вол. И в этом случае пользователю уже не нужно нажимать дополнительно клавишу
Ниже приведены две формы объявления метода ReadKey(). static ConsoleKeyInfo ReadKey() static ConsoleKeyInfo ReadKey(bool intercept)
В первой форме данного метода ожидается нажатие клавиши. Когда оно проис ходит, метод возвращает введенный с клавиатуры символ и выводит его на экран. Во второй форме также ожидается нажатие клавиши, и затем возвращается введенный с клавиатуры символ. Но если значение параметра intercept равно true, то введен ный символ не отображается. А если значение параметра intercept равно false, то введенный символ отображается.
Метод ReadKey() возвращает информацию о нажатии клавиши в объекте типа ConsoleKeyInfo, который представляет собой структуру, состоящую из приведенных ниже свойств, доступных только для чтения. char KeyChar ConsoleKey Key ConsoleModifiers Modifiers
Свойство KeyChar содержит эквивалент char введенного с клавиатуры символа, свойство Key – значение из перечисления ConsoleKey всех клавиш на клавиатуре, а свойство Modifiers – описание одной из модифицирующих клавиш (, или ), которые были нажаты, если это действительно имело место, при фор мировании ввода с клавиатуры. Эти модифицирующие клавиши представлены в перечислении ConsoleModifiers следующими значениями: Control, Shift и Alt. В свойстве Modifiers может присутствовать несколько значений нажатых модифи цирующих клавиш.
Главное преимущество метода ReadKey() заключается в том, что он предоставляет средства для организации ввода с клавиатуры в диалоговом режиме, поскольку этот ввод не буферизуется построчно. Для того чтобы продемонстрировать данный метод в действии, ниже приведен соответствующий пример программы. // Считать символы, введенные с консоли, используя метод ReadKey(). using System; class ReadKeys { static void Main() { ConsoleKeyInfo keypress; Console.WriteLine("Введите несколько символов, " + "а по окончании – ."); do { keypress = Console.ReadKey(); // считать данные о нажатых клавишах Console.WriteLine(" Вы нажали клавишу: " + keypress.KeyChar); // Проверить нажатие модифицирующих клавиш. if((ConsoleModifiers.Alt & keypress.Modifiers) != 0) Console.WriteLine("Нажата клавиша
Вот, например, к какому результату может привести выполнение этой программы. Введите несколько символов, а по окончании – . а Вы нажали клавишу: а b Вы нажали клавишу: b d Вы нажали клавишу: d А Вы нажали клавишу: А Нажата клавиша
Как следует из приведенного выше результата, всякий раз, когда нажимается клави ша, метод ReadKey() немедленно возвращает введенный с клавиатуры символ. Этим он отличается от упоминавшегося ранее метода Read(), в котором ввод выполняется с построчной буферизацией. Поэтому если требуется добиться в программе реакции на ввод с клавиатуры, то рекомендуется выбрать метод ReadKey(). Запись данных в поток вывода на консоль
Потоки Console.Out и Console.Error являются объектами типа TextWriter. Вывод на консоль проще всего осуществить с помощью методов Write() и WriteLine(), с которыми вы уже знакомы. Существуют варианты этих методов для вывода данных каждого из встроенных типов. В классе Console определяются его соб ственные варианты метода Write() и WriteLine(), и поэтому они могут вызываться непосредственно для класса Console, как это было уже не раз показано на страницах данной книги. Но при желании эти и другие методы могут быть вызваны и для класса TextWriter, который является базовым для потоков Console.Out и Console.Error.
Ниже приведен пример программы, в котором демонстрируется вывод в потоки Console.Out и Console.Error. По умолчанию данные в обоих случаях выводятся на консоль. // Организовать вывод в потоки Console.Out и Console.Error. using System; class ErrOut { static void Main() { int a=10, b=0; int result; Console.Out.WriteLine("Деление на нуль приведет " + "к исключительной ситуации."); try { result = a / b; // сгенерировать исключение при попытке деления на нуль } catch(DivideByZeroException exc) { Console.Error.WriteLine(exc.Message); } } }
При выполнении этой программы получается следующий результат. Деление на нуль приведет к исключительной ситуации. Попытка деления на нуль.
Начинающие программисты порой испытывают затруднения при использова нии потока Console.Error. Перед ними невольно встает вопрос: если оба потока, Console.Out и Console.Error, по умолчанию выводят результат на консоль, то за чем нужны два разных потока вывода? Ответ на этот вопрос заключается в том, что стандартные потоки могут быть переадресованы на другие устройства. Так, поток Console.Error можно переадресовать в выходной файл на диске, а не на экран. Это, например, означает, что сообщения об ошибках могут быть направлены в файл журна ла регистрации, не мешая выводу на консоль. И наоборот, если вывод на консоль пере адресуется, а вывод сообщений об ошибках остается прежним, то на консоли появятся сообщения об ошибках, а не выводимые на нее данные. Мы еще вернемся к вопросу переадресации после рассмотрения файлового ввода-вывода. Класс FileStream и байтовый ввод-вывод в файл
В среде .NET Framework предусмотрены классы для организации ввода-вывода в файлы. Безусловно, это в основном файлы дискового типа. На уровне операционной системы файлы имеют байтовую организацию. И, как следовало ожидать, для ввода и вывода байтов в файлы имеются соответствующие методы. Поэтому ввод и вывод в файлы байтовыми потоками весьма распространен. Кроме того, байтовый поток вво да или вывода в файл может быть заключен в соответствующий объект символьного потока. Операции символьного ввода-вывода в файл находят применение при обра ботке текста. О символьных потоках речь пойдет далее в этой главе, а здесь рассматри вается байтовый ввод-вывод.
Для создания байтового потока, привязанного к файлу, служит класс FileStream. Этот класс является производным от класса Stream и наследует всего его функции.
Напомним, что классы потоков, в том числе и FileStream, определены в простран стве имен System.IO. Поэтому в самом начале любой использующей их программы обычно вводится следующая строка кода. using System.IO; Открытие и закрытие файла
Для формирования байтового потока, привязанного к файлу, создается объект класса FileStream. В этом классе определено несколько конструкторов. Ниже при веден едва ли не самый распространенный среди них: FileStream(string путь, FileMode режим)
где путь обозначает имя открываемого файла, включая полный путь к нему; а ре жим – порядок открытия файла. В последнем случае указывается одно из значений, определяемых в перечислении FileMode и приведенных в табл. 14.4. Как правило, этот конструктор открывает файл для доступа с целью чтения или записи. Исключе нием из этого правила служит открытие файла в режиме FileMode.Append, когда файл становится доступным только для записи.
Таблица 14.4. Значения из перечисления FileMode Значение Описание FileMode.Append Добавляет выводимые данные в конец файла FileMode.Create Создает новый выходной файл. Существующий файл с таким же именем будет разрушен FileMode.CreateNew Создает новый выходной файл. Файл с таким же именем не должен существовать FileMode.Open Открывает существующий файл FileMode.OpenOrCreate Открывает файл, если он существует. В противном случае создает новый файл FileMode.Truncate Открывает существующий файл, но сокращает его длину до нуля
Если попытка открыть файл оказывается неудачной, то генерируется исклю чение. Если же файл нельзя открыть из-за того что он не существует, генерируется исключение FileNotFoundException. А если файл нельзя открыть из-за какой– нибудь ошибки ввода-вывода, то генерируется исключение IOException. К чис лу других исключений, которые могут быть сгенерированы при открытии фай ла, относятся следующие: ArgumentNullException (указано пустое имя файла), ArgumentException (указано неверное имя файла), ArgumentOutOfRangeException (указан неверный режим), SeaurityException (у пользователя нет прав доступа к файлу), PathTooLongException (слишком длинное имя файла или путь к нему), NotSupportedException (в имени файла указано устройство, которое не поддержи вается), а также DirectoryNotFoundException (указан неверный каталог).
Исключения PathTooLongException, DirectoryNotFoundException и FileNotFoundException относятся к подклассам класса исключений IOException. Поэтому все они могут быть перехвачены, если перехватывается исключение IOException.
Ниже в качестве примера приведен один из способов открытия файла test.dat для ввода. FileStream fin; try { fin = new FileStream("test", FileMode.Open); } catch(IOException exc) { // перехватить все исключения, связанные с вводом-выводом Console.WriteLine(exc.Message); // Обработать ошибку. } catch(Exception exc { // перехватить любое другое исключение. Console.WriteLine(exc.Message); // Обработать ошибку, если это возможно. // Еще раз сгенерировать необрабатываемые исключения. }
В первом блоке catch из данного примера обрабатываются ошибки, возникающие в том случае, если файл не найден, путь к нему слишком длинен, каталог не существу ет, а также другие ошибки ввода-вывода. Во втором блоке catch, который является "универсальным" для всех остальных типов исключений, обрабатываются другие веро ятные ошибки (возможно, даже путем повторного генерирования исключения). Кроме того, каждую ошибку можно проверять отдельно, уведомляя более подробно о ней и принимая конкретные меры по ее исправлению.
Ради простоты в примерах, представленных в этой книге, перехватывается только ис ключение IOException, но в реальной программе, скорее всего, потребуется перехва тывать и другие вероятные исключения, связанные с вводом-выводом, в зависимости от обстоятельств. Кроме того, в обработчиках исключений, приводимых в качестве примера в этой главе, просто уведомляется об ошибке, но зачастую в них должны быть запрограм мированы конкретные меры по исправлению ошибок, если это вообще возможно. Напри мер, можно предложить пользователю еще раз ввести имя файла, если указанный ранее файл не был найден. Возможно, также потребуется сгенерировать исключение повторно.
Как упоминалось выше, конструктор класса FileStream открывает файл, доступный для чтения или записи. Если же требуется ограничить доступ к файлу только для чтения или же только для записи, то в таком случае следует использовать такой конструктор. FileStream(string путь, FileMode режим, FileAccess доступ)
Как и прежде, путь обозначает имя открываемого файла, включая и полный путь к нему, а режим – порядок открытия файла. В то же время доступ обозначает кон кретный способ доступа к файлу. В последнем случае указывается одно из значений, определяемых в перечислении FileAccess и приведенных ниже. FileAccess.Read FileAccess.Write FileAccess.ReadWrite
Например, в следующем примере кода файл test.dat открывается только для чтения. FileStream fin = new FileStream("test.dat", FileMode.Open, FileAccess.Read);
По завершении работы с файлом его следует закрыть, вызвав метод Close(). Ниже приведена общая форма обращения к этому методу. void Close()
При закрытии файла высвобождаются системные ресурсы, распределенные для этого файла, что дает возможность использовать их для другого файла. Любопытно, что метод Close() вызывает, в свою очередь, метод Dispose(), который, собственно, и высвобождает системные ресурсы.
ПРИМЕЧАНИЕ Оператор using, рассматриваемый в главе 20, предоставляет еще один способ закры тия файла, который больше не нужен. Такой способ оказывается удобным во многих случаях обращения с файлами, поскольку гарантирует закрытие ненужного больше файла простыми средствами. Но исключительно в целях демонстрации основ обращения с файлами, в том числе и того момента, когда файл может быть закрыт, во всех примерах, представленных в этой главе, используются явные вызовы метода Close(). Чтение байтов из потока файлового ввода-вывода