Стандартные потоки ввода вывода в консольном приложении net framework

Обновлено: 07.07.2024

На самом низком уровне иерархии потоков ввода-вывода находятся потоки, оперирующие байтами. Это объясняется тем, что многие устройства при выполнении операций ввода-вывода ориентированы на байты. Однако для человека привычнее оперировать символами, поэтому разработаны символьные потоки, которые фактически представляют собой оболочки, выполняющие преобразование байтовых потоков в символьные и наоборот. Кроме этого, реализованы потоки для работы с int -, double -, short - значениями, которые также представляют оболочку для байтовых потоков, но работают не с самими значениями, а с их внутренним представлением в виде двоичных кодов.

  1. FileStream - байтовый поток, разработанный для файлового ввода-вывода
  2. BufferedStream - заключает в оболочку байтовый поток и добавляет буферизацию, которая во многих случаях увеличивает производительность программы;
  3. MemoryStream - байтовый поток, который использует память для хранения данных.

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

Подробно мы рассмотрим класс FileStream , классы StreamWriter и StreamReader , представляющие собой оболочки для класса FileStream и позволяющие преобразовывать байтовые потоки в символьные, а также классы BinaryWriter и BinaryReader , представляющие собой оболочки для класса FileStream и позволяющие преобразовывать байтовые потоки в двоичные для работы с int -, double -, short - и т.д. значениями.

Байтовый поток

Чтобы создать байтовый поток, связанный с файлом, создается объект класса FileStream . При этом в классе определено несколько конструкторов. Чаще всего используется конструктор, который открывает поток для чтения и/или записи:

  1. параметр filename определяет имя файла, с которым будет связан поток ввода-вывода данных; при этом filename определяет либо полный путь к файлу, либо имя файла, который находится в папке bin/debug вашего проекта.
  2. параметр mode определяет режим открытия файла, который может принимать одно из возможных значений, определенных перечислением FileMode :
    • FileMode.Append - предназначен для добавления данных в конец файла;
    • FileMode.Create - предназначен для создания нового файла, при этом если существует файл с таким же именем, то он будет предварительно удален;
    • FileMode. CreateNew - предназначен для создания нового файла, при этом файл с таким же именем не должен существовать;
    • FileMоde.Open - предназначен для открытия существующего файла;
    • FileMode.ОpenOrCreate - если файл существует, то открывает его, в противном случае создает новый
    • FileMode.Truncate - открывает существующий файл, но усекает его длину до нуля

Если попытка открыть файл оказалась неуспешной, то генерируется одно из исключений: FileNotFoundException - файл невозможно открыть по причине его отсутствия, IOException - файл невозможно открыть из-за ошибки ввода-вывода, ArgumentNullException - имя файла представляет собой null -значение, ArgumentException - некорректен параметр mode, SecurityException - пользователь не обладает правами доступа, DirectoryNotFoundException - некорректно задан каталог.

Другая версия конструктора позволяет ограничить доступ только чтением или только записью:

  1. параметры filename и mode имеют то же назначение, что и в предыдущей версии конструктора;
  2. параметр how, определяет способ доступа к файлу и может принимать одно из значений, определенных перечислением FileAccess :
  1. FileAccess.Read - только чтение;
  2. FileAccess.Write - только запись;
  3. FileAccess.ReadWrite - и чтение, и запись.

После установления связи байтового потока с физическим файлом внутренний указатель потока устанавливается на начальный байт файла.

Для чтения очередного байта из потока, связанного с физическим файлом, используется метод ReadByte() . После прочтения очередного байта внутренний указатель перемещается на следующий байт файла. Если достигнут конец файла, то метод ReadByte() возвращает значение -1 .

Для побайтовой записи данных в поток используется метод WriteByte() .

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

Рассмотрим пример использования класса FileStream , для копирования одного файла в другой. Но вначале создадим текстовый файл text.txt в папке bin/debug текущего проекта. И внесем в него произвольную информацию, например:

Задание. Подумайте, почему для переменной i указан тип int . Можно было бы указать тип byte ?

Символьный поток

Чтобы создать символьный поток нужно поместить объект класса Stream (например, FileStream ) "внутрь" объекта класса StreamWriter или объекта класса StreamReader . В этом случае байтовый поток будет автоматически преобразовываться в символьный.

Класс StreamWriter предназначен для организации выходного символьного потока. В нем определено несколько конструкторов. Один из них записывается следующим образом:

где параметр stream определяет имя уже открытого байтового потока.

Например, создать экземпляр класса StreamReader можно следующим образом:

Этот конструктор генерирует исключение типа ArgumentException , если поток stream не открыт для вывода, и исключение типа ArgumentNullException , если он (поток) имеет null-значение.

Другой вид конструктора позволяет открыть поток сразу через обращения к файлу:

где параметр name определяет имя открываемого файла.

Например, обратиться к данному конструктору можно следующим образом:

И еще один вариант конструктора StreamWriter :

где параметр name определяет имя открываемого файла;

параметр appendFlag может принимать значение true - если нужно добавлять данные в конец файла, или false - если файл необходимо перезаписать.

Теперь для записи данных в поток fileOut можно обратиться к методу WriteLine . Это можно сделать следующим образом:

В данном случае в конец файла t.txt будет дописано слово test .

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

где параметр stream определяет имя уже открытого байтового потока.

Этот конструктор генерирует исключение типа ArgumentException , если поток stream не открыт для ввода.

Например, создать экземпляр класса StreamWriter можно следующим образом:

Как и в случае с классом StreamWriter у класса StreamReader есть и другой вид конструктора, который позволяет открыть файл напрямую:

где параметр name определяет имя открываемого файла.

Обратиться к данному конструктору можно следующим образом:

Параметр Encoding.GetEncoding(1251) говорит о том, что будет выполняться преобразование из кода Windows-1251 (одна из модификаций кода ASCII, содержащая русские символы) в Unicode. Encoding.GetEncoding(1251) реализован в пространстве имен System.Text .

Теперь для чтения данных из потока fileIn можно воспользоваться методом ReadLine . При этом если будет достигнут конец файла, то метод ReadLine вернет значение null.

Рассмотрим пример, в котором данные из одного файла копируются в другой, но уже с использованием классов StreamWriter и StreamReader .

Задание. Выясните, для чего предназначен метод ReadToEnd() и когда имеется смысл его применять.

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


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

Потоки

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

По умолчанию, при создании потока при помощи класса Thread мы получим foreground-поток. Для того, чтобы его поменять на фоновый, мы можем воспользоваться свойством thread.IsBackground.

В приложениях, которые имеют пользовательский интерфейс (UI), всегда есть как минимум один главный (GUI) поток, который отвечает за состояние компонентов интерфейса. Важно знать, что возможность изменять состояние представления есть только у этого, так называемого «UI-потока», который создается для приложения обычно в единственном экземпляре (хотя и не всегда).

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

Потоки — это дорогостоящие объекты, которые занимают память, могут использовать различные ресурсы системы и находиться в разных состояниях. Для их создания требуется время. В сравнении с процессами они менее ресурсоемки, но все же требуют довольно больших затрат на создание и уничтожение. Более того, за освобождение занимаемых конкретным потоком ресурсов отвечает разработчик. Например, для выполнения массы небольших задач неэффективно запускать множество потоков, так как издержки на их запуск могут превысить выгоду от использования. Для того, чтобы иметь возможность повторно использовать уже запущенные потоки и избавиться от издержек на создание, был введен так называемый пул-потоков (ThreadPool).

ThreadPool

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

Потоки внутри пула разделяются на две группы: worker и I/O-потоки. Рабочие потоки фокусируются на работе, связанной с загрузкой CPU (CPU based), в то время как I/O-потоки — на работе с устройствами ввода/вывода: файловая система, сетевая карта и другие. Если пытаться выполнять I/O-операцию на рабочем потоке (CPU based), то это будет напрасная трата ресурсов, так как поток будет находиться в состоянии ожидания завершения I/O-операции. Для подобных задач предназначены отдельные I/O-потоки. При использовании пула потоков это скрыто в явном виде от разработчиков. Получить количество разных потоков в пуле можно при помощи кода:

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

Запустить задачу на выполнение при помощи потока, взятого в пуле, можно с помощью:

  • класса ThreadPool: ThreadPool.QueueUserWorkItem
  • асинхронных делегатов (пара методов делегата: BeginInvoke() и EndInvoke())
  • класса BackgroundWorker
  • TPL (Task Parallel Library, о которой мы еще поговорим ниже)
  • Потокам из пула невозможно назначить имя
  • Потоки из пула всегда фоновые (background)
  • Блокировка потоков из пула может привести к запуску дополнительных потоков и падению производительности
  • Вы можете поменять приоритет потоку из пула, но он вернется в дефолтное значение (normal) после возвращения в пул

Синхронизация

При построении многопоточного приложения необходимо гарантировать, что любая часть разделяемых данных защищена от возможности изменения их значений множеством потоков. Учитывая, что управляемая куча является одним из разделяемых потоками ресурсов, а все потоки в AppDomain имеют параллельный доступ к разделяемым данным приложения, очевидно, что доступ к таким общим данным необходимо синхронизировать. Это гарантирует, что в один момент времени доступ к определенному блоку кода получит лишь один поток (или указанное количество, в случае использования Семафора). Таким образом, мы можем гарантировать целостность данных, а также их актуальность в любой момент времени. Давайте рассмотрим возможные варианты синхронизации и частые проблемы. Говоря о синхронизации, обычно выделяют 4 вида:

  • Блокировка вызывающего кода
  • Конструкции, ограничивающие доступ к кускам кода
  • Сигнализирующие конструкции
  • Неблокирующая блокировка

Blocking

Под блокировкой понимается ожидание одним потоком завершения другого или нахождение в режиме ожидания в течение некоего времени. Обычно реализуется при помощи методов класса Thread: Sleep() и Join(), метода EndInvoke() асинхронных делегатов или при помощи тасков (Task) и их механизмов ожидания. Следующие конструкции являются примерами плохого подхода к реализации ожидания:

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

Похожим примером может быть следующая конструкция:

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

Locking


В таблице представлены самые популярные механизмы для организации блокировок. При помощи Мютексов можно реализовать межпроцессорную блокировку (а не только для нескольких потоков одного процесса). Семафор отличается от Мютекса тем, что позволяет указать количество потоков или процессов, которые могут получить одновременный доступ к конкретному участку кода. Конструкция lock, которая является вызовом пары методов: Monitor.Enter() и Monitor.Exit(), применяется очень часто, поэтому рассмотрим возможные проблемы и рекомендации по её использованию.

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

При использовании блокирования при помощи ключевого слова lock следует помнить о следующих правилах:

    необходимо избегать блокирования типов:

Signaling

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


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

Nonblocking synchronization

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

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

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

Collections

Полезно знать, что в пространстве имен System.Collections.Concurrent определено довольно много потокобезопасных коллекций для разных задач. Самые распространенные:

BlockingCollection
ConcurrentBag
ConcurrentDictionary<TKey, TValue>
ConcurrentQueue
ConcurrentStack

В большинстве случаев нет смысла в реализации собственной подобной коллекции — намного проще и разумней использовать готовые протестированные классы.

Асинхронность

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

Покажем на наглядном примере разницу между синхронным и асинхронным подходами.

Предположим, вы хотите пообедать пиццей в офисе и у вас есть два варианта:

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

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

Эволюция

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

В версии 2.0 была введена новая модель под названием EAP (Event-based Asynchronous Pattern). Класс, поддерживающий асинхронную модель, основанную на событиях, будет содержать один или несколько методов MethodNameAsync. Он может отражать синхронные версии, которые выполняют то же действие с текущим потоком. Также в этом классе может содержаться событие MethodNameCompleted и метод MethodNameAsyncCancel (или просто CancelAsync) для отмены операции. Данный подход распространен при работе с сервисами. В Silverlight применяется для обращения к серверной части, а Ajax по сути представляет из себя реализацию данного подхода. Стоит опасаться длинных цепочек связанных вызовов событий, когда по завершении одной долгосрочной операции в событии ее завершения вызывается следующая, потом еще следующая и так далее. Это чревато дэдлоками и непредвиденными результатами. Обработка исключений и результаты асинхронной операции доступны только в обработчике события посредством соответствующих свойств параметра: Error и Result.

В последних версиях фреймворка появились новые возможности на основе все тех же задач, которые упрощают написание асинхронного кода и делают его более читабельным и понятным. Для этого введены новые ключевые слова async и await, которыми помечаются асинхронные методы и их вызовы. Асинхронный код становится очень похожим на синхронный: мы просто вызываем нужную операцию и весь код, который следует за ее вызовом, автоматически будет завернут в некий «колбек», который вызовется после завершения асинхронной операции. Также данный подход позволяет обрабатывать исключения в синхронной манере; явно дожидаться завершения операции; определять действия, которые должны быть выполнены, и соответствующие условия. Например, мы можем добавить код, который будет выполнен только в том случае, если в асинхронной операции было сгенерировано исключение. Но не все так просто, даже несмотря на массу информации на эту тему.

async\await

Рассмотрим основные рекомендации по использованию этих ключевых слов, а также некоторые интересные примеры. Чаще всего рекомендуется использовать асинхронности «от начала до конца». Это подразумевает использование только одного подхода в конкретном вызове или функциональном блоке, не смешивайте синхронные вызовы с асинхронными. Классический пример данной проблемы:

Также рекомендуется избегать конструкций вида async void (асинхронный метод, который ничего не возвращает). Async-методы могут возвращать значения Task, Task<ТResult> и void. Последний вариант был оставлен для поддержки обратной совместимости и позволяет добавлять асинхронные обработчики событий. Но стоит помнить про некоторые специфичные отличия подобных методов, а именно:

  • Исключения нельзя перехватить стандартными средствами
  • Поскольку подобные методы не возвращают задачу, то мы ограничены в работе с такими конструкциями. К примеру, мы не сможем ожидать завершения подобных задач стандартными средствами или создать цепочку выполнения, как в случае с объектами Task.
  • Подобные методы сложно тестировать, так как они имеют различия в обработке ошибок и композиции.

Данная рекомендация очень актуальна при разработке каких-либо библиотек, которые ничего не знают про GUI.

Рассмотрим еще несколько примеров применения новых ключевых слов, а также некоторые особенности их использования:


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

Заключение

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

Использование многопоточности в приложениях с GUI обычно влечет за собой дополнительные ограничения, не забывайте о них!

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

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

Файлы и каталоги

Типы в пространстве имен System.IO можно использовать для взаимодействия с файлами и каталогами. Например, можно получать и задавать свойства файлов и каталогов, а также извлекать коллекции файлов и каталогов на основе критерия поиска.

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

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

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

Directory предоставляет статические методы для создания, перемещения и перечисления в каталогах и подкаталогах.

DirectoryInfo предоставляет методы экземпляра для создания, перемещения и перечисления в каталогах и подкаталогах.

Path предоставляет методы и свойства для обработки строк каталогов межплатформенным способом.

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

Помимо использования этих классов, пользователи Visual Basic могут использовать методы и свойства, предоставляемые классом Microsoft.VisualBasic.FileIO.FileSystem для файлового ввода-вывода.

Потоки

Абстрактный базовый класс Stream поддерживает чтение и запись байтов. Все классы, представляющие потоки, являются производными от класса Stream. Класс Stream и его производные классы обеспечивают общий способ просмотра источников данных и хранилищ объектов, а также изолируют программиста от специфических особенностей операционной системы и базовых устройств.

Потоки включают три основные операции:

Чтение — перенос информации из потока в структуру данных, такую как массив байтов.

Запись — перенос данных в поток из источника данных.

Поиск — определение и изменение текущей позиции внутри потока.

В зависимости от базового источника или хранилища данных поток может поддерживать лишь некоторые из этих возможностей. Например, класс PipeStream не поддерживает поиск. Свойства CanRead, CanWrite и CanSeek потока определяют операции, поддерживаемые потоком.

Ниже перечислены некоторые часто используемые классы потока:

FileStream — для чтения и записи в файл.

IsolatedStorageFileStream — для чтения и записи в файл в изолированном хранилище.

MemoryStream — для чтения и записи в память в качестве резервного хранилища.

BufferedStream — для повышения быстродействия операций чтения и записи.

NetworkStream — для чтения и записи на сетевые сокеты.

PipeStream — для чтения и записи в анонимные и именованные каналы.

CryptoStream — для связи потоков данных с криптографическими преобразованиями.

Пример асинхронной работы с потоками см. в разделе Асинхронный файловый ввод-вывод.

Средства чтения и записи

Пространство имен System.IO также предоставляет типы для чтения закодированных символов из потоков и их записи в потоки. Как правило, потоки предназначены для ввода и вывода байтов. Типы чтения и записи обрабатывают преобразование закодированных символов в байты или из байтов, чтобы поток мог завершить операцию. Каждый класс чтения и записи связан с потоком, который можно получить с помощью свойства класса BaseStream .

Ниже перечислены некоторые часто используемые классы для чтения и записи:

BinaryReader и BinaryWriter — для чтения и записи простых типов данных, таких как двоичные значения.

StreamReader и StreamWriter — для чтения и записи символов с использованием закодированного значения для преобразования символов в байты или из байтов.

StringReader и StringWriter — для чтения и записи символов в строки или из строк.

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

Асинхронные операции ввода-вывода

Чтение и запись больших объемов данных может быть ресурсоемкой. Эти задачи необходимо выполнять асинхронно, если приложение должно продолжать отвечать на запросы пользователя. В случае синхронных операций ввода-вывода поток пользовательского интерфейса будет заблокирован до тех пор, пока ресурсоемкая операция не завершится. При разработке приложений Microsoft Store для Windows 8.x используйте асинхронные операции ввода-вывода, чтобы не создавалось впечатления, что приложение прекратило свою работу.

Имена асинхронных элементов содержат Async , например: CopyToAsync, FlushAsync, ReadAsync и WriteAsync. Используйте эти методы с ключевыми словами async и await .

Дополнительные сведения см. в разделе Асинхронный файловый ввод-вывод.

Сжатие

При сжатии и распаковке файлов и потоков часто используются следующие классы:

ZipArchive — для создания и восстановления содержимого ZIP-архива.

ZipArchiveEntry — для представления сжатого файла.

ZipFile — для создания, извлечения и открытия сжатого пакета.

ZipFileExtensions — для создания и извлечения содержимого из сжатого пакета.

DeflateStream — для сжатия и распаковки потоков с помощью алгоритма Deflate.

GZipStream — для сжатия и распаковки потоков в формате gzip.

См. практическое руководство по Сжатие и извлечение файлов.

Изолированное хранилище

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

Изолированное хранилище недоступно для приложений Microsoft Store для Windows 8.x. Вместо этого используйте классы данных приложения в пространстве имен Windows.Storage. Дополнительные сведения см. в разделе Данные приложения.

Часто используются следующие классы, реализующие изолированное хранилище:

IsolatedStorage предоставляет базовый класс для реализации изолированного хранилища.

IsolatedStorageFile предоставляет область изолированного хранилища, в которой содержатся файлы и каталоги.

IsolatedStorageFileStream представляет файл в изолированном хранилище.

Операции ввода-вывода в приложениях Microsoft Store

Следует отметить некоторые важные различия в использовании операций ввода-вывода в приложениях Microsoft Store для Windows 8.x:

Изолированное хранилище недоступно; вместо этого используйте данные приложения.

Используйте асинхронные методы, такие как ReadAsync и WriteAsync, чтобы предотвратить блокировку потока пользовательского интерфейса.

Дополнительные сведения об операциях ввода-вывода в приложении Microsoft Store для Windows 8.x: Краткое руководство. Чтение и запись файлов.

Ввод-вывод и безопасность

При использовании классов в пространстве имен System.IO необходимо выполнить требования безопасности операционной системы, такие как списки управления доступом для контроля доступа к файлам и каталогам. Это требование дополняет остальные требования FileIOPermission. Списками управления доступом можно управлять программно. Дополнительные сведения см. в разделе Практическое руководство. Добавление или удаление записей списка управления доступом.

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

См. также

Распространенные задачи ввода-вывода
Содержит список задач ввода-вывода, связанных с файлами, каталогами и потоками, а также ссылки на соответствующее содержимое и примеры для каждой задачи.

Асинхронный файловый ввод-вывод
Описывает преимущества и основные операции асинхронного ввода и вывода.

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

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

Backend-разработка и создание утилит командной строки невозможно без консольного ввода\вывода. В рамках данной статьи будут рассмотрены методы класса Console для чтения и записи информации в консоль.

Исходный код примеров из этой статьи можете скачать из нашего github-репозитория.

Консольный ввод\вывод

Класс Console. Вывод данных в консоль

Класс Console содержит методы для работы с консольным вводом\выводом, управлением потоком с ошибками и окном консоли. Начнем с вывода информации в консоль, для решения этой задачи класс Console предоставляет два метода: Write и WriteLine.

Для знакомства с методами класса Console создайте в удобном для вас каталоге проект с именем CSharpLesson3:

Метод Write

Откройте файл Program.cs в созданном ранее проекте и добавьте в метод Main следующие строки, демонстрирующие работу с методом Write:

Более подробно про форматирование будет написано в уроке, посвященном работе со строками.

Сохраните документ, откройте консоль, перейдите в каталог с проектом и выполните команду:

Результат работы программы будет выглядеть примерно следующим образом:

Обратите внимание на первые три вызова метода Write, если не поставить символ перевода строки, то данные будут выдаваться в одну строку друг за другом, это не всегда удобно. Если требуется, чтобы вывод каждый раз осуществлялся с новой строки, то воспользуйтесь методом WriteLine.

Метод WriteLine

Метод WriteLine записывает указанные данные в выходной поток и добавляет символ перевода строки. Добавьте в программу следующие строки:

Теперь надпись “Текущая дата:” и дата со временем будут выведены в разных строках.
WriteLine (также как Write) может принимать не только строковые значения, но и переменные разных типов:

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

Запустите проект и в консоли увидите результат выполнения программы:

Прием интерполяции позволяет внутри строки, используя фигурные скобки, указывать имена переменных, которые будут заменены значениями. Перед такой строкой ставится знак $.
Методы WriteLine и Write позволяют использовать форматирование:

Класс Console. Чтение данных из консоли

В классе Console, помимо рассмотренных выше методов вывода в консоль, присутствуют методы для считывания вводимых данных из консоли.

Метод Read

Если после надписи “Нажмите любую клавишу, а затем Enter” будет введено более одного символа, то Read вернет только первый.

Для представления кода в виде символа необходимо переменную key1 привести к типу Char, для этого можно воспользоваться методом Convert.ToChar().

Метод ReadLine

Метод ReadLine возвращает введенную строку символов до нажатия клавиши “Enter”. Добавим несколько строк в файл с исходным кодом, для демонстрации работы с методом ReadLine :

Если необходимо получить с консоли численные данные, то следует воспользоваться методами из класса Convert для приведения типа String к соответствующему числовому типу:

Метод ReadKey

ReadKey возвращает объект класса ConsoleKeyInfo, в который помещается информация о нажатой клавише. Этот метод, как правило, используют для остановки выполнения программы или чтобы получить ответ от пользователя. Добавим строки для работы с ReadKey:

Исходный код примеров из этой статьи можете скачать из нашего github-репозитория.

Читайте также: