Потоки (Streams) и ввод-вывод (IO) в C#

Ввод-вывод строится на основе потоков. Большинство классов для работы с потоками и вводом-выводом находится в пространстве имен System.IO.

Потоковая архитектура

В основе потоковой архитектуры .NET лежат три понятия:

  • опорное хранилище (backing store)
  • декоратор (decorator)
  • адаптер (adapter)

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

Чтобы использовать опорное хранилище его нужно открыть. Этой цели и служат потоки, которые в .NET представлены классом System.IO.Stream, содержащий методы для чтения, записи и  позиционирования потоков.

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

Потоки делятся на две категории:

  • потоки опорных хранилищ — потоки, жестко привязанные к конкретным типам опорных хранилищ, такие как FileStream или NetworkStream
  • потоки-декораторы — наполняют другие потоки, трансформируя данные тем или иным способом, такие как DeflateStream или CryptoStream

Декораторы освобождают потоки опорных хранилищ от необходимости самостоятельно реализовывать такие вещи, как сжатие и шифрование. Декораторы можно подключать во время выполнения, а также соединять их в цепочки (т.е. использовать несколько декораторов в одном потоке).

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

Потоки

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

Пример:

Кроме того класс содержит асинхронные методы для чтения и записи: ReadAsync иWriteAsync:

Чтение и запись

Поток может поддерживать чтение, запись или то и другое. Если свойство CanWriteвозвращает false — поток предназначен только для чтения, если CanRead возвращаетfalse — поток предназначен только для записи.

Метод Read читает блок данных из потока и записывает их в массив. Он возвращает количество полученных байтов, которое может быть либо равно, либо меньше значения аргумента count. Если оно меньше count — это значит, что достигнут конец потока или поток выдает данные порциями меньшего размера (в связи с этим однозначно судить о том, что достигнут конец потока можно только если метод возвращает 0):

Метод ReadByte читает один байт из потока, в случае достижения конца потока возвращает -1.

Методы Write и WriteByte отправляют данные в поток, в случае неудачи генерируют исключение.

Аргумент offset методов Read и Write ссылается на индекс в массиве buffer, с которого начинается чтение или запись, а не на позицию в потоке.

Позиционирование (Seeking)

Если свойство CanSeek возвращает true, то поток поддерживает возможность позиционирования. Для такого потока можно запрашивать свойство Length и модифицировать его с помощью метода SetLength. Также можно в любой момент изменять свойство Position, отражающее позицию относительно начала потока, в которой производится чтение или запись. Метод Seek позволяет перемещаться относительно текущей позиции или относительно конца потока.

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

Закрытие и сброс

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

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

Закрытие потока с декоратором закрывает и декоратор и поток с опорным хранилищем. В случае цепочки декораторов закрытие самого внешнего декоратора закрывает всю цепочку.

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

Тайм-ауты

Если свойство CanTimeout возвращает true, поток поддерживает тайм-аут чтения и записи (тайм-ауты поддерживают сетевые потоки, а файловые и потоки в памяти — нет). Для таких потоков свойство ReadTimeout задает тайм-аут в миллисекундах на чтение, а свойство WriteTimeout — тайм-аут на запись. Ноль означает отсутствие тайм-аута. При наступлении тайм-аута методы Read и Write генерируют исключения.

Потоки с опорными хранилищами в .NET

Основными потоками с опорными хранилищами в .NET являются:

  • System.IO.FileStream
  • System.IO.MemoryStream
  • System.IO.IsolatedStorageFileStream
  • System.Net.Sockets.NetworkStream
  • System.IO.Pipes.PipeStream

Декораторы в .NET

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

  • System.IO.BufferedStream
  • System.IO.Compression.DeflateStream
  • System.IO.Compression.GZipStream
  • System.Security.Cryptography.CryptoStream
  • System.Net.Security.AuthenticatedStream

Адаптеры потоков в .NET

Текстовые адаптеры (для типов string и char):

  • TextReader
  • TextWriter
  • StreamReader
  • StreamWriter
  • StringReader
  • StringWriter

Двоичные адаптеры (для типов int, bool, string и float):

  • BinaryReader
  • BinaryWriter

Адаптеры XML:

  • XmlReader
  • XmlWriter

FileStream

Создать экземпляр FileStream можно с помощью статических методов класса File:

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

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

Класс File определяет также более удобные статические методы, позволяющие открыть файл и прочитать/записать его содержимое за одни шаг:

  • File.ReadAllText — читает целый файл и возвращает его содержимое в виде одной строки
  • File.ReadAllLines — читает целый файл и возвращает его содержимое в виде массива строк
  • File.ReadAllBytes — читает целый файл и возвращает его содержимое в виде байтового массива
  • File.WriteAllText — записывает строку в файл
  • File.WriteAllLines — записывает массив строк в файл
  • File.WriteAllBytes — записывает байтовый массив в файл
  • File.AppendAllText — добавляет строку в конец файла

Метод File.ReadLines не загружает весь файл в память, а возвращает лениво-оцениваемое IEnumerable<string>:

Имя файла

Имя файла может быть абсолютным или относительным к текущему каталогу. Узнать текущий каталог или изменить его можно с помощью статического свойстваEnvironment.CurrentDirectory. Текущий каталог может как совпадать, так и не совпадать с директорией, в которой расположен исполняемый файл программы.

Свойство AppDomain.CurrentDomain.BaseDirectory возвращает базовый каталог приложения, который как правило совпадает с директорией, содержащей исполняемый файл программы.

Чтобы указать имя файла относительно полученного каталога, можно использовать метод Path.Combine:

Допустимо использовать сетевые имена, такие как \\JoesPC\PicShare\pic.jpgили \\10.1.1.2\PicShare\pic.jpg.

Режимы файла (FileMode)

Все конструкторы FileStream помимо имени файла требуют указания режима файла — значения enum FileMode:

  • FileMode.CreateNew — будет создан новый файл, доступный для чтения и записи; если файл с таким именем уже существует, будет выброшено исключение
  • FileMode.Create — тоже самое, что и FileMode.CreateNew, но если файл с таким именем уже существует, он будет перезаписан новым (содержимое существующего файла будет обнулено)
  • FileMode.OpenOrCreate — если файл существует — он будет открыт для чтения и записи, если не существует — будет создан новый (доступный для чтения и записи)
  • FileMode.Open — файл открывается для чтения и записи; если файла с таким именем не существует, будет выброшено исключение
  • FileMode.Truncate — тоже самое, что и FileMode.Open, но содержимое открытого файла обнуляется
  • FileMode.Append — файл открывается только для записи; запись производится в конец файла

Метод File.Create и значение FileMode.Create приведут к генерации исключения, если используются для скрытых файлов. Чтобы перезаписать скрытый файл, его придется сначала удалить и создать заново.

Во всех случаях, кроме FileMode.Append, поток открывается для чтения и записи. Можно ограничить уровень доступа, если в качестве дополнительного параметра передать enum FileAccess:

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

  • enum FileShare — уровень доступа для других процессов, пока мы работает с файлом (None, Read (по умолчанию), ReadWrite или Write)
  • размер внутреннего буфера в байтах
  • флаг, указывающий следует ли отложить асинхронный вывод для ОС
  • объект FileSecurity, описывающий права доступа для пользователей для создаваемого файла
  • enum FileOptions:
    • FileOptions.Encrypted — включает шифрование на уровне ОС
    • FileOptions.DeleteOnClose — автоматическое удаление при закрытии временных файлов
    • FileOptions.RandomAccess и FileOptions.SequentialScan
    • FileOptions.WriteThrough — отключение кэширования при записи

Если файл открыт с ключом FileShare.ReadWrite позволяет другим процессам читать и записывать файл. С помощью методов класса FileStream Lock и Unlockможно блокировать и разблокировать участки файла:

Если часть или вся запрошенная область файла уже заблокированы метод Lockвыбросит исключение.

MemoryStream

Класс MemoryStream в качестве опорного хранилища использует массив (целиком расположенный в памяти).

С помощью методов ToArray и GetBuffer можно преобразовать MemoryStream в байтовый массив.

Закрытие и сброс MemoryStream являются не обязательными. После закрытия выполнять чтение и запись потока нельзя. Метод Flush потока MemoryStreamвообще ничего не делает.

PipeStream

Класс PipeStream предоставляет простой способ взаимодействия одного процесса с другим через протокол каналов Windows. Канал — это низкоуровневая конструкция, которая позволяет отправлять и получать байты (или сообщения — группы байтов). Различают два вида каналов:

  • анонимный канал — обеспечивает однонаправленное взаимодействие между родительским и дочерним процессом на одном компьютере
  • именованный канал — обеспечивает двунаправленное взаимодействие между произвольными процессами на одном компьютере или на разных (через сеть)

PipeStream является абстрактным классом с четырьмя конкретными подтипами: два применяются для анонимных каналов (AnonymousPipeServerStream иAnonymousPipeClientStream), два для именованных (NamedPipeServerStream и NamedPipeClientStream).

Именованные потоки

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

  • сервер создает экземпляр NamedPipeServerStream и вызывает его метод WaitForConnection
  • клиент создает экземпляр NamedPipeClientStream и вызывает его метод Connect (с необязательным тайм-аутом)

После этого участники для взаимодействия осуществляют чтение и запись в потоки:

Для поддержки сообщений длиннее одного байта каналы предлагают режим передачи сообщений. Когда он включен, участник читающий поток, может узнать, что сообщение завершено, проверив свойство потока IsMessageComplete. Включить режим передачи сообщений на стороне сервера можно указав PipeTransmissionMode.Message при создании экземпляра потока, а на стороне клиента — присвоив значение PipeTransmissionMode.Message свойству потока ReadMode после вызова Connect:

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

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

Анонимные потоки

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

  • сервер создает экземпляр AnonymousPipeServerStream и устанавливает его направление через свойство PipeDirection, которое может быть In или Out
  • сервер вызывает метод GetClientHandleAsString для получения дескриптора канала, который затем передается клиенту (обычно как аргумент при запуске дочернего процесса)
  • клиент создает экземпляр AnonymousPipeClientStream, указывая ему противоположное направление (свойство PipeDirection)
  • родительский и дочерний процессы взаимодействуют посредством чтения и записи в потоки
  • сервер освобождает локальный дескриптор за счет вызова метода DisposeLocalCopyOfClientHandle

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

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

BufferedStream

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

В примере поток FileStream помещается в декоратор BufferedStream с буфером в 20Kb. При вызова ReadByte фактически из потока FileStream  считывается не один байт, а 20 000 байтов (размер буфера), которые помещаются в буфер. При следующих 19 999 вызовах метода ReadByte данные будут читаться из буфера, а не из файлового потока. На практике соединение FileStream с BufferedStreamизбыточно, так как FileStream сам поддерживает буферизацию.

Закрытие BufferedStream автоматически закрывает лежащий в основе поток с опорным хранилищем.

Текстовые адаптеры

TextReader и TextWriter

TextReader и TextWriter являются абстрактными базовыми классами для адаптеров, которые имеют дело исключительно с символами и строками. Каждый из них имеет две реализации:

  • StreamReader и StreamWriter — для хранения данных используют классStream и транслируют байты потока в символы или строки
  • StringReader и StringWriter — для хранения данных используют строки

Члены класса TextReader:

Метод Peek возвращает следующий символ из потока, не перемещая текущую позицию вперед. Метод Peek и Read (без аргументов) возвращают -1 при достижении конца потока или целочисленное значение, которое может быть приведено к char.

Перегруженная версия Read, принимающая буфер char[], идентична по функционалу методу ReadBlock.

Метод ReadLine выполняет чтение до тех пор, пока не встретит символы новой строки (CR, LF либо CR+LF). Он возвращает строку отбрасывая символы новой строки.

Члены класса TextWriter:

Методы Write и WriteLine дополнительно перегружены, чтобы принимать каждый из примитивных типов, а также тип object (они просто вызывают ToString на том, что им передано).

Метод WriteLine дополняет переданную ему строку последовательностью CR+LF. Свойство NewLine позволяет заменить эту последовательность на другие символы.

Классы TextReader и TextWriter также определяют асинхронные версии методов чтения и записи.

StreamReader и StreamWriter

Сами по себе классы TextReader и TextWriter являются абстрактными и никак не связаны ни с потоком, ни с опорным хранилищем. А вот реализующие их типы StreamReader и StreamWriter имеют в своей основе байтовый поток и выполняют преобразование между символами и байтами.

Для создания экземпляра StreamReader или StreamWriter их конструкторам необходимо передать байтовый поток:

Класс File предоставляет статические методы CreateText, AppendText и OpenText, которые позволяют создать экземпляр StreamReader или StreamWriter более лаконично:

Достижение конца файла можно проверить с помощью метода Peek (вернет -1) или метода ReadLine (вернет null).

Преобразование байтов потока в строку выполняется с помощью класса System.Text.Encoding, который можно передать в конструктор StreamReader или StreamWriter. По умолчанию используется кодировка UTF-8.

StringReader и StringWriter

Адаптеры StringReader и StringWriter вообще не имеют дела с потоками. Вместо этого они используют в качестве лежащего в основе источника данных строку или экземпляр StringBuilder. Они не выполняют никаких преобразований байтов в строку и обратно, по сути они не делают ничего такого, что нельзя было бы сделать с помощью строк и StringBuilder. Преимущество же их в том, что они имеют общий базовый класс с типами StreamReader и StreamWriter, облегчая их преобразования.

Двоичные адаптеры

Классы BinaryReader и BinaryWriter выполняют чтение и запись в поток предопределенных типов: bool, byte, chardecimal, float, double, short, int,long, sbyte, ushort, uint и ulong, а также строк и массивов предопределенных типов. В отличие от текстовых адаптеров двоичные адаптеры сохраняют предопределенные символы эффективнее, так как они представлены в памяти.

Класс BinaryReader может также выполнять чтение в байтовый массив:

Закрытие адаптеров потока

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

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

При закрытии адаптера и потока без использования инструкции using, нужно всегда сначала закрывать или сбрасывать адаптер, и только после этого закрывать поток, иначе любые данные, буферизированные в адаптере, будут утеряны.

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

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

Если конструкторы адаптера StreamReader/StreamWriter в качестве четвертого параметра передать true, то после его освобождения лежащий в основе поток закрыт не будет:

Сжатие потоков

В пространстве имен System.IO.Compression доступно два декоратора для сжатия потоков: DeflateStream и GZipStream. Отличаются они тем, что GZipStreamзаписывает дополнительную информацию в начале и в конце, а также соответствует стандарту. Оба класса являются декораторами: они сжимают или распаковывают данные из другого потока, который указывается при создании их экземпляра:

В этом же пространстве имен определяются еще два класса: ZipArchive и ZipFile, которые использую популярный алгоритм сжатия, применяемый в zip-файлах, что делает их совместимыми с zip-файлами, созданными в других приложениях, а также позволяет сжимать несколько файлов в один архив.

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

Метод CreateFromDirectory класса ZipFile добавляет все файлы из указанного каталога в zip-архив:

Метод ExtractToDirectory того же класса извлекает содержимое zip-архива в указанный каталог:

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

Экземпляр ZipArchive можно создать либо с помощью конструктора, передав ему поток (объект Stream), либо с помощью статического метода Open класс ZipFile, передав ему имя файла и действие, которое должно быть произведено над архивом —Read (чтение), Create (создание) или Update (обновление). Свойство Entriesобъекта ZipArchive возвращает коллекцию входящих в архив файлов, а метод GetEntry позволяет найти конкретный файл:

Класс ZipArchiveEntry инкапсулирует отдельный файл в архиве. Он имеет методыDelete (позволяет удалить файл из архива), ExtractToFile (позволяет извлечь файл из архива) и Open (возвращает экземпляр Stream, с возможностью чтения/записи). Создавать новые файлы в архиве можно с помощью методов CreateEntry иCreateEntryFromFile класса ZipArchive.

Манипулирование файлами и каталогами

Пространство имен System.IO содержит ряд типов, предназначенных для манипулирования файлами и каталогами, позволяющих копировать, перемещать, создавать файлы и каталоги, устанавливать их атрибуты и права доступа. Сюда входят статические классы File и Directory, экземплярные классы FileInfo иDirectoryInfo, а также  статический класс Path, позволяющий манипулировать строками путей к файлам и каталогам.

Класс File

File — статический класс, все методы которого принимают имя файла. Имя файла может быть абсолютным или относительным (относительно текущего каталога). Класс включает следующие статические методы:

Методы Move и Replace позволяют переименовать файл или переместить его в другой каталог, при этом Move генерирует исключение, если файл назначения существует, а Replace этого не делает.

Метод Delete удаляет файл, а если файл помечен как предназначенный только для чтения метод сгенерирует исключение UnauthorizedAccessException.

Метод GetAttributes возвращает enum FileAttribute со следующими значениями, которые можно комбинировать:

  • Archive
  • Compressed
  • Device
  • Directory
  • Encrypted
  • Hidden
  • Normal
  • NotContentIndexed
  • Offline
  • ReadOnly
  • ReparsePoint
  • SparseFile
  • System
  • Temporary

С помощью метода SetAttributes атрибуты файла можно менять:

С помощью класса FileInfo это можно сделать лаконичней:

Атрибуты Compressed и Encrypted (сжатие и шифрование) с помощью методаSetAttribute изменить нельзя. Для шифрования и дешифрования предназначены методы Encrypt и Decrypt класса File. Для сжатия класс File методов не содержит.

Методы GetAccessControl и SetAccessControl позволяют получать и задавать права доступа ОС через объект FileSecurity (пространство имен System.Security.AccessControl). Этот объект также можно передать в конструктор FileStream для указания прав доступа при создании файла.

Класс Directory

Статический класс Directory содержит методы аналогичные методам классаFile: для проверки существования каталога (Exists), перемещения каталога (Move), удаления каталога (Delete), получения/установки времени создания и последнего доступа, получения/установки прав доступа. Кроме того Directory включает следующие статические методы:

Методы Enumerate* эффективней методов Get*, поскольку извлекают данные только при перечислении. И те и другие перегружены и могут принимать searchPattern(string) и searchOption (enum). При указании SearchOption.SearchAllSubDirectories также будет выполняться рекурсивный поиск в подкаталогах. Методы *FileSystemEntries сочетают в себе функционал методов *Files и *Directories.

FileInfo и DirectoryInfo

Если над файлом или каталогом требуется выполнить последовательность операций, то более удобными будут экземплярные методы FileInfo и DirectoryInfo.

Класс FileInfo предлагает большинство статических методов класс File в форме экземплярных методов, а также содержит ряд дополнительных свойств — Extension,Length, IsReadOnly и Directory. Последний возвращает объект DirectoryInfo.

Пример использования класса DirectoryInfo:

Path

Статический класс Path содержит методы и поля для работы с путями и именами файлов:

Метод Combine объединяет каталог и имя файла, при необходимости дополняя имя каталога косой чертой. Метод GetFullPath преобразует относительный путь в абсолютный. Метод GetRandomFileName возвращает уникальное случайное имя файла в формате 8.3, не создавая при этом файл. Метод GetTempFileName генерирует временное имя файла с применением автоинкрементного счетчика и затем создает пустой файл с этим именем в локальном временном каталоге.

Специальные директории

В перечисленных выше классах отсутствуют средства нахождения специальных директорий Windows, таких как My Documents, Program Files и др. Сделать это можно с помощью метода GetFolderPath класса System.Environment:

Значения enum Environment.SpecialFolder охватывают все специальные каталоги в Windows:

  • AdminTools
  • CommonVideos
  • Personal
  • ApplicationData
  • Cookies
  • PrinterShortcuts
  • CDBurning
  • Desktop
  • ProgramFiles
  • CommonAdminTools
  • DesktopDirectory
  • ProgramFilesX86
  • CommonApplicationData
  • Favorites
  • Programs
  • CommonDesktopDirectory
  • Fonts
  • Recent
  • CommonDocuments
  • History
  • Resources
  • CommonMusic
  • InternetCache
  • SendTo
  • CommonOemLinks
  • LocalApplicationData
  • StartMenu
  • CommonPictures
  • LocalizedResources
  • Startup
  • CommonProgramFiles
  • MyComputer
  • System
  • CommonProgramFilesX86
  • MyDocuments
  • SystemX86
  • CommonPrograms
  • MyMusic
  • Templates
  • CommonStartMenu
  • MyPictures
  • UserProfile
  • CommonStartup
  • MyVideos
  • Windows
  • CommonTemplates
  • NetworkShortcuts

Использование папок ApplicationData (настройки, перемещаемые с пользователем по сети), LocalApplicationData (неперемещаемые настройки пользователя), CommonApplicationData (настройки для всех пользователей компьютера) для сохранения данных и настроек приложения более предпочтительно, чем использование системного реестра. Как правило в этих директориях создаются подкаталоги с именем, совпадающим с названием приложения.

Информация о диске

Запрашивать информацию об устройствах на компьютере можно с помощью класса DriveInfo:

enum DriveType содержит следующие  значения:

  • Unknown
  • NoRootDirectory
  • Removable
  • Fixed
  • Network
  • CDRom
  • Ram

Перехват событий файловой системы

Класс FileSystemWatcher позволяет отслеживать действия над каталогами и их подкаталогами: создание, модификация, переименование, удаление файлов и поддиректорий, а также изменение их атрибутов. Событие генерируется независимо от того, кем оно совершено — пользователем или процессом:

Размещенные в памяти файлы (Memory-Mapped Files)

Типы для размещенных в памяти файлов находятся в пространстве имен System.IO.MemoryMappedFiles. Размещенные в памяти файлы дают две ключевые возможности:

  • эффективный произвольный доступ к файловым данным
  • возможность разделения памяти между различными процессами на одном компьютере

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

Чтобы создать размещенный в памяти файл необходимо:

  • получить объект FileStreams
  • создать экземпляр MemoryMappedFile, передав конструктору файловый поток
  • вызвать метод CreateViewAccessor на объекте MemoryMappedFile

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

Методу CreateFromFile можно также передать имя размещенного в памяти файла и емкость.

Указание имени отличного от null позволяет разделять блок памяти с другими процессами. Хотя обычно для этого используется другой метод — CreateNew: один из процессов создает блок разделяемой памяти, вызывая MemoryMappedFile.CreateNew и передает ему имя создаваемого размещенного в памяти файла, другой процесс подписывается на этот блок памяти, вызывая метод MemoryMappedFile.OpenExisting с таким же именем.

Методы аксессора представления

Метод CreateViewAccessor объекта MemoryMappedFile создает аксессор представления — объект MemoryMappedViewAccessor, позволяющий выполнять чтение и запись в произвольных позициях.

Методы Read*/Write* принимают числовые типы, bool, char, массивы и структуры (содержащие элементы и поля значимых типов). Ссылочные типы и их массивы/структуры запрещены. Поэтому чтобы записать строку ее нужно закодировать в байтовый массив:

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

Каждая программа .NET имеет доступ к локальной области хранения, уникальной для этой программы, которая называется изолированным хранилищем. Это хранилище удобно, когда программа не имеет доступа к стандартной файловой системе (приложения Silverlight и некоторые интернет приложения), но его использование обладает рядом недостатков (неудобство работы, большие ограничения).

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

  • GetUserStoreForDomain
  • GetMachineStoreForDomain
  • GetUserStoreForAssembly
  • GetMachineStoreForAssembly

Затем он используется для создания объекта IsolatedStorageFileStream:

Также можно создать объект IsolatedStorageFile вызвав статический метод IsolatedStorageFile.GetStore, передав ему правильную комбинацию флагов StorageScope:

Один комментарий к “Потоки (Streams) и ввод-вывод (IO) в C#”

  1. Хорошая статья. Спасибо! Было бы намного удобнее читать, если бы была подсветка синтаксиса.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *