Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Books / 3_C#_2005_для_чайников_(Дэвис-2008)

.pdf
Скачиваний:
86
Добавлен:
24.03.2015
Размер:
15.46 Mб
Скачать

Где именно вы расположите проект, зависит от того, как вы хотите организовать ваше решение. Вы можете поместить папку ClassLibraryDriver в той же общей папке, что и папку ClassLibrary, или вложить ее в папку ClassLi­ brary. Демонстрационная программа ClassLibrary в этом разделе придер­ живается первого подхода.

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

4.Щелкните на кнопке ОК для того, чтобы закрыть диалоговое окно New Project и создать проект.

Мастер Visual Studio АррWizard создаст папку проекта вместе с файлами проекта. В окне Solution Explorer вы увидите два проекта: ClassLibrary и ClassLi­ braryDriver.

5.Щелкните правой кнопкой мыши на проекте ClassLibraryDriver и вы­ берите в меню Set as Startup Project.

Тем самым вы указываете С#, где находится функция Main () для данного реше­ ния. Она должна находиться в сборке драйвера, но не в сборке библиотеки классов.

6.В файле Program, cs проекта ClassLibraryDriver добавьте в функцию Main () исходный текст наподобие приведенного:

MyLibrary myLib = new MyLibrary(); // Или что именно вы

// хотите вызывать

//вызов библиотечной функции myLib.LibraryFunctionl();

//Вызов статической функции

int result = MyLibrary.LibraryFunction2();

Другими словами, напишите программу, которая использует классы и методы из библиотеки. Вы уже сталкивались с этим ранее как в данной главе, так и в других главах книги — например, когда вызывали метод WriteLine () класса Con­ sole из библиотеки .NET Framework. (Console находится в пространстве имен

System в файле mscorlib . dll.)

Код примера библиотеки и драйвера приведен выше — см. листинг демонстраци­ онной программы ClassLibrary.

7.Выберите команду меню Projects Add Reference.

8.В диалоговом окне Add Reference щелкните на вкладке Projects. Выберите ваш проект ClassLibrary и щелкните на кнопке ОК.

Вы можете также добавить директиву using для пространства имен ClassLi­ brary в файл Program, cs проекта ClassLibraryDriver, чтобы сэконо­ мить на набираемом тексте.

Влюбой программе, которую вы напишете в будущем, достаточно включить дирек­ тиву using для пространства имен вашей библиотеки классов и добавить ссылку на

.DLL-файл, содержащий библиотеку, чтобы иметь возможность использовать библио­ течные классы в своей программе. Именно так программы в данной книге применяют классы из библиотеки .NET Framework.

Глава 19. Работа с файлами и библиотеками

433

Хранение данных в файлах

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

Классы для работы с файлами определены в пространстве имен System.IO. Базо вым классом для файлового ввода-вывода является класс FileStream. Для работы с файлом

программист должен его открыть. Команда open подготавливает файл

к работе и возвращает его дескриптор. Обычно дескриптор — это просто число, которое используется всякий раз при чтении из файла или записи в него.

Асинхронный ввод-вывод: есть ли что-то хуже ожидания?

Обычно программа ожидает завершения ее запроса на ввод-вывод и только затем продолжает выполнение. Вызовите метод read ( ) , и в общем случае вы не получите управление назад до тех пор, пока данные из файла не будут считаны

Такой способ работы называется синхронным вводом-выводом.

Классы С# System.IO поддерживают также и асинхронный ввод-вывод. При ис­ пользовании асинхронного ввода-вывода вызов read () тут же вернет управление программе, позволяя ей заниматься чем-то еще, пока ее запрос на чтение данных из файла выполняется в фоновом режиме. Программа может проверить флаг выполнения запроса, чтобы узнать, завершено ли его выполнение.

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

Асинхронный ввод-вывод может существенно повысить производительность про­ граммы, но при этом вносит дополнительный уровень сложности.

С# использует более интуитивный подход. Он связывает каждый файл с объектом класса FileStream. Конструктор класса FileStream открывает файл, а методы FileStream выполняют операции ввода-вывода.

FileStream— не единственный класс, который может осуществлять файловый ввод-вывод. Однако он предоставляет хорошую основу для работы с файлами, выполняя 90% всех ваших нужд по работе с ними. Это корневой класс, описываемый в данном разделе. Он достаточно хорош для С# и для вас.

FileStream— фундаментальный класс. Весь набор его действий — это открытие фай­ ла, чтение и запись блока байтов. К счастью, пространство имен System.І0 содержит, по­ мимо прочего, следующий набор классов, которые обернуты вокруг FileStream и предос­ тавляют более простые и богатые возможности.

BinaryReader/BinaryWriter — пара потоковых классов, которые содержат методы для чтения и записи каждого из типов-значений: ReadChar (), Write-

434

Часть VII. Дополнительные главы

Char (), ReadByte (), WriteByte () и так далее. Эти классы полезны для чте­ ния и записи объекта в бинарном (не читаемом человеком) формате, в противопо­ ложность текстовому формату. Для работы с бинарными данными можно исполь­ зовать массив или коллекцию элементов типа Byte.

TextReader/TextWriter— пара классов для чтения символов (текста). Эти классы предоставляются в двух видах (наборах подклассов): StringReader/StringWriter и StreamReader/StreamWriter.

StringReader/StringWriter— простые потоковые классы, которые огра­ ничены чтением и записью строк. Они позволяют рассматривать строку как файл, предоставляя альтернативу доступу к символам строк с помощью записи с ис­ пользованием квадратных скобок ( [ ] ) , цикла foreach или методов класса

String наподобие Split (), Concatenate () и IndexOf ( ) . Вы считываете и записываете строки почти так же, как и файлы. Этот метод полезен для длинных файлов с сотнями или тысячами символов, которые вы хотите обработать вместе. Методы в этих классах аналогичны методам классов StreamReader и StreamWriter, описываемым далее.

StreamReader/StreamWriter

более интеллектуальные классы чтения

и записи текста. Например, класс

StreamWriter имеет метод WriteLine (),

очень похожий на метод класса Console. StreamReader имеет соответствую­ щий метод ReadLine () и очень удобный метод ReadToEnd ( ) , собирающий весь текстовый файл в одну группу и возвращающий считанные символы как строку string (которую вы можете затем использовать с классом StringReader, циклом foreach и тому подобным).

TextReader/TextWriter применяются как сами по себе, так и их более удобные подклассы, такие как StreamReader/StreamWriter.

В следующем разделе будут рассмотрены демонстрационные программы FileWrite и FileRead, которые иллюстрируют способы использования классов для тек­ стового ввода-вывода.

Использование Stream Writer

Программы генерируют два вида вывода.

Некоторые программы пишут блоки данных в виде байтов в чисто бинар­ ном формате. Этот тип вывода полезен для эффективного сохранения объ­ ектов (например, файл объектов Student, которые сохраняются между запусками программы в файле на диске).

Большинство программ читает и записывает информацию в виде текста, который может читать человек. Классы StreamWriter и StreamReader являются наиболее гибкими классами для работы с данными в таком виде.

Данные в удобном для чтения человеком виде ранее назывались ASCIIстроками, а сейчас — ANSI-строками. Эти два термина указывают названия организаций по стандартизации, которые определяют соответствующие стандарты. Однако кодировка ANSI работает только с латинским алфавитом

Глава 19. Работа с файлами и библиотеками

435

и не имеет кириллических символов, символов иврита, арабского языка или хины не говоря уж о такой экзотике, как корейские, японские или китайские иероглифы Гораздо более гибким является стандарт Unicode, который включает ANSI-символ как свою начальную часть, а кроме них — массу других алфавитов, включая все пе речисленные выше. Unicode имеет несколько форматов, именуемых кодировками форматом по умолчанию для С# является UTF8.

Приведенная далее демонстрационная программа FileWrite считывает строки данных с консоли и записывает их в выбранный пользователем файл

// FileWrite - запись ввода с консоли в текстовый файл using System;

using System.10; // Требуется для работы с файлами namespace FileWrite

{

- public class Program

{

public static void Main(string [] args)

//Создание объекта для имени файла — цикл while

//позволяет пользователю продолжать попытки до тех

//пор, пока файл не будет успешно открыт StreamWriter sw = null;

string sFileName = ""; while(true)

{

try

{

//

Ввод имени

файла для вывода (просто Enter для

//

завершения

программы)

Console.Write("Введите имя файла "

 

 

+ "(пустое имя для завершения):");

sFileName = Console.ReadLine();

if

(sFileName.Length == 0)

{

 

 

// Имени файла нет — выходим из цикла break;

}

//Открываем файл для записи; если файл уже

//существует, генерируем исключение:

//FileMode.CreateNew - для создания файла, если

//он еще не существует и генерации исключения при

//наличии такого файла; FileMode.Append для

//создания нового файла или добавления данных к

//существующему файлу; FileMode.Create для

//создания нового файла или урезания уже

//имеющегося до нулевого размера. Возможные

//варианты FileAccess: FileAccess.Read,

//FileAccess.Write, FileAccess.ReadWrite FileStream fs = File.Open(sFileName,

FileMode.CreateNew,

FileAccess.Write);

// Генерируем файловый поток с UTF8-символами (по

436

Часть VII. Дополнительные главы

//умолчанию второй параметр дает UTF8, так что он

//может быть опущен)

sw = new StreamWriter(fs, System.Text.Encoding.UTF8);

//

Считываем по

одной строке, выводя

каждую из них

//

в FileStream

для записи

 

 

 

Console.WriteLine("Введите

текст

" +

 

 

 

"(пустую

строку

для

выхода)");

while(true)

 

 

 

 

{

//Считываем очередную строку с консоли; если

//строка пуста, завершаем цикл

string slnput = Console.ReadLine(); if (slnput.Length == 0)

{

break;

}

// Записываем считанную строку в файл вывода sw.WriteLine(slnput);

}

// Закрываем созданный файл sw.Close();

sw = null; // Желательно обнуление ссылочных // переменных после использования

}

catch(IOException fe)

{

//Произошла ошибка при работе с файлом — о ней

//надо сообщить пользователю вместе с полным

//именем файла

string sDir = Directory.GetCurrentDirectory(); string s = Path.Combine(sDir, sFileName); Console.WriteLine("Ошибка с файлом {О}", s ) ;

//Теперь выводим сообщение об ошибке из

//исключения

Console.WriteLine(fe.Message);

//Ожидаем подтверждения пользователя Console.WriteLine("Нажмите <Enter> для " +

"завершения программы...");

Console.Read();

Демонстрационная программа FileWrite использует пространства имен Sys­ tem. 10 и System. Пространство имен System. 10 содержит классы, предназначенные дня работы с файлами.

Обратитесь к разделу справочной системы, посвященному пространству имен Sys - tern.Text. Одним из его более полезных классов является StringBuilder, ко­ торый предоставляет эффективный подход для работы со сложными строками, со­ стоящими из нескольких частей. Это гораздо более эффективный способ работы, чем использование оператора + для конкатенации большого количества строк.

Глава 19. Работа с файлами и библиотеками

437

Программа начинает работу с функции Main ( ) , которая включает цикл while со держащий try-блок. В этом нет ничего необычного для программ, работающих с фай лами. (В следующем разделе, где описывается работа с классом StreamReader,ис пользуется немного другой подход, дающий те же результаты.)

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

Цикл while служит двум следующим целям.

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

Команда break в программе переносит вас за try-блок, тем самым предоставляет удобный механизм для выхода из функции или программы. Не забывайте о том; что break работает только в пределах цикла, в котором вызвана эта команда,

Демонстрационная программа FileWrite считывает имя создаваемого файла с ком соли. Программа прекращает работу путем выхода из цикла с помощью команды break, если пользователь вводит пустое имя файла. Ключ к программе заключается в следую­ щих строках:

FileStream fs = File.Open(sFileName, FileMode.CreateNew, FileAccess.Write);

// ...

sw = new StreamWriter(fs, System.Text.Encoding.UTF8);

В первой строке программа создает объект FileStream, который представляет файл, записываемый на диск. Конструктор FileStream использует следующие три ар­ гумента.

Имя файла: это просто имя файла, который следует открыть. Простое имя файла наподобие filename. txt предполагает, что файл находится в текущем каталоге (для демонстрационной программы FileWrite это подкаталог \bin\Debugn каталоге проекта; словом, это каталог, в котором находится сам . ЕХЕ-файл). Имя файла, начинающееся с обратной косой черты наподобие \directory\ filename.txt, рассматривается как полный путь на локальной машине. Имя файла, начинающееся с двух обратных косых черт (например, \\machine\ directory\filename.txt), указывает файл, расположенный на другой ма­ шине в вашей сети. Кодировка файла — существенно более сложный вопрос, вы­ ходящий за рамки данной книги.

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

(CreateNew), добавление к файлу (Append) и перезапись (Create). Creat­ eNew создает новый файл, но генерирует исключение IOException, если такой файл уже существует. Простой режим Create создает файл, если он отсутствует, но если он есть, то просто перезаписывает его. И наконец, Append создает файл,

438

Часть VII. Дополнительные главы

Подобие StreamWriter.WriteLine() больше, чем простое совпадение.
И наконец, файл закрывается с помощью вызова

если он не существует, но если он имеется, открывает его для дописывания ин­ формации в конец файла.

Тип доступа: файл может быть открыт для чтения, записи или для обеих операций.

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

В следующей строке программа "оборачивает" вновь открытый файловый объект

FileStream в объект StreamWriter. Класс StreamWriter служит оберткой для объекта FileStream, которая предоставляет набор методов для работы с текстом.

Такой вид "оборачивания" одного класса вокруг другого представляет собой полез­ ный программный шаблон проектирования— StreamWriter "оборачивается" (содержит ссылку) вокруг другого класса FileStream и расширяет интерфейс FileStream, добавляя некоторые мелкие удобства. Методы StreamWriter вызывают методы внутреннего объекта FileStream. Это — рассматривавшееся в главе 12, "Наследование", отношение СОДЕРЖИТ.

Первый аргумент конструктора StreamWriter— объект FileStream. Второй аргу­ мент указывает используемую кодировку. Кодировка по умолчанию — UTF8.

Вы не должны указывать кодировку при чтении файла. Дело в том, что StreamWriter записывает тип применяемой кодировки в первых трех байтах файла. StreamReader счи­ тывает эти три байта при открытии файла и определяет тип используемой кодировки. Сокры­ тие такого рода деталей представляет собой одно из преимуществ хорошей библиотеки.

Затем программа FileWrite начинает чтение строк, вводимых с консоли. Программа завершает работу при считывании пустой строки, но до этого она собирает все считанные строки и записывает их, используя метод WriteLine () класса StreamWriter.

и Console.WriteLine()—

sw. Close ().

Обратите внимание, что программа обнуляет ссылку sw по закрытии файла. Файловый объект становится бесполезен после того, как файл закрыт. Правила хорошего тона требуют обнулять ссылки после того, как они становятся недей­ ствительны, так, чтобы обращений к ним больше не было (если вы попытаетесь это сделать, то будет сгенерировано исключение).

Блок catch напоминает футбольного вратаря: он стоит здесь для того, чтобы ловить все исключения, которые могут быть сгенерированы в программе. Он выводит сообще­ ние об ошибке, включая имя вызвавшего ее файла. Однако выводится не просто имя файла, а его полное имя, включая путь к нему. Это делается посредством класса Direc­ tory, который позволяет получить текущий каталог и добавить его перед введенным именем файла с использованием метода Path. Combine () (Path — класс, разработан-

Глава 19. Работа с файлами и библиотеками

439

ный для работы с информацией о путях, a Directory предоставляет свойства и метода для работы с каталогами).

Путь — это полное имя каталога. Например, если имя файла — с: \user\directory\! text. txt, то его путь — с : \user\directory.

Метод Combine () достаточно интеллектуален, чтобы разобраться, что для файла наподобие c:\test.txt Path О не является текущим каталогом Path. Combine () представляет также наиболее безопасный путь, гарантирую­ щий корректное объединение двух частей пути, включая символ-разделитель (\) между ними. (В Windows символ-разделитель пути — \. Вы можете получить корректный разделитель для операционной системы, под управлением которой запущена программа, с помощью Path.DirectorySeparatorChar. Библио­ тека .NET Framework изобилует такого рода возможностями, существенно облег­ чая программистам на С# написание программ, которые должны работать под управлением нескольких операционных систем.)

Достигнув конца цикла w h i l e — либо после выполнения try-блока, либо после блока catch, — программа возвращается к началу цикла и позволяет пользователю за­ писать другой файл.

Вот как выглядит пример выполнения демонстрационной программы (пользовательский ввод выделен полужирным шрифтом).

Введите имя файла (пустое имя для завершения):TestFilel.txt Введите текст (пустую,строку для выхода)

Это какой-то текст

Иеще

Иеще раз...

Введите имя файла (пустое имя для завершения):TestFilel.txt Ошибка с файлом С:\C#Programs\FileWrite\bin\Debug\TestFilel.txt The file already exists.

Введите имя файла (пустое имя для завершения):TestFile2.txt Введите текст (пустую строку для выхода)

Я ошибся - мне надо было ввести имя файла TestFile2.

Введите имя файла (пустое имя для завершения):

Нажмите <Enter> для завершения программы...

Все отлично работает, пока некоторый текст вводится в файл TestFilel. txt. Но при попытке открыть файл TestFilel.txt заново программа выводит сообщение The file already exists (файл уже существует). Обратите внимание на полный путь к файлу, выводимый вместе с сообщением об ошибке. Если исправить ошибку и ввести имя TestFile2 . txt, все продолжает отлично работать.

Повышение скорости чтения с использованием StreamReader

Запись файла — дело стоящее, но совершенно бесполезное, если вы не мо­ жете позже прочесть записанное. Приведенная далее демонстрационная про­ грамма считывает текстовый файл, например, созданный демонстрационной программой FileWrite или с помощью Блокнота.

440

Часть VII. Дополнительные главы

Глава :

// FileRead - читает текстовый файл и

выводит на

консоль его

// содержимое

 

 

 

using System;

 

 

 

using System. 10;

 

 

 

namespace

FileRead

 

 

 

{

 

 

 

 

public

class Program

 

 

 

public static void Main(string []

args)

 

{

 

 

 

 

// Нам нужен объект для чтения файла

 

StreamReader sr;

 

 

 

string sFileName = " ";

 

 

//

Пытаемся получить

корректное

имя файла до тех пор,

//

пока наконец его

не получим

(для выхода

из

//программы надо использовать комбинацию клавиш

//<Ctrl+C>)

while(true)

{

try

{

// Ввод имени файла

Console.Write("Введите имя текстового файла:"); sFileName = Console.ReadLine();

//Если пользователь ничего не ввел, генерируем

//исключение для указания, что такой ввод

//неприемлем

if (sFileName.Length == 0)

{

throw new

IOException("Введено пустое имя файла");

}

//Открываем файловый поток для чтения; если файл

//не существует - не создаем его

FileStream fs = File.Open(sFileName,

F i1eMode.Open,

FileAccess.Read);

//Преобразуем в StreamReader - этот класс

//использует первые три байта файла для

//определения использованной кодировки (но не для

//языка)

sr = new StreamReader(fs, true); break;

}

// Сообщение об ошибке с указанием имени файла catch(IOException fe)

{

Console.WriteLine("{0}\n\n", fe.Message);

}

}

// Чтение содержимого файла Console.WriteLine("\пСодержимое файла:") ; try

Глава 19. Работа с файлами и библиотеками

441

{

// Чтение по одной строке while(true)

{

//

Считывание строки

string slnput = sr.ReadLine() ;

//

Выход,

когда больше считать строку не удается

if

(slnput

== null)

{

break;

}

// Вывод считанного на консоль

Console.WriteLine(slnput);

}

catch(IOException fe)

// перехватывает все ошибки и сообщает о них Console.Write(fe.Message);

// Закрываем файл (игнорируя возможные ошибки) try

sr.Close();

catch {} sr = null;

// Ожидаем подтверждения пользователя Console.WriteLine("Нажмите <Enter> для " +

"завершения программы.. . ") ;

Console.Read();

}

Вспомним, что текущим каталогом, использовавшимся демонстрационной програм­ мой FileRead, был подкаталог \bin\Debug в каталоге проекта FileRead (но не ка­ талог \bin\Debug в каталоге проекта FileWrite). Перед тем как вы запустите про­ грамму FileRead, поместите текстовый файл в подкаталог \bin\Debug каталогам проекта и запомните имя этого файла, чтобы впоследствии вы могли открыть его. Дли этого вполне подойдет копия файла TestFilel.txt, созданного демонстрационно! программой FileWrite.

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

Одно из ограничений подхода, использованного в демонстрационной программе Fil­ eRead, заключается в том, что попытки получить имя файла от пользователя продолжают­ ся до бесконечности. Если пользователь ошибается, он должен продолжать свои попытки. Единственный выход из программы без ввода корректного имени — воспользоваться ком­ бинацией клавиш <Ctrl+C> либо щелкнуть на кнопке закрытия консольного окна.

442

Часть VII. Дополнительные глава