Содержание
Ввод-вывод строится на основе потоков. Большинство классов для работы с потоками и вводом-выводом находится в пространстве имен System.IO
.
Потоковая архитектура
В основе потоковой архитектуры .NET лежат три понятия:
- опорное хранилище (backing store)
- декоратор (decorator)
- адаптер (adapter)
Опорное хранилище — это конечная точка ввода-вывода: файл, сетевое подключение и т.д. Оно может представлять собой либо источник, из которого последовательно считываются байты, либо приемник, куда байты последовательно записываются, либо и то и другое вместе.
Чтобы использовать опорное хранилище его нужно открыть. Этой цели и служат потоки, которые в .NET представлены классом System.IO.Stream
, содержащий методы для чтения, записи и позиционирования потоков.
Потоки не загружают опорное хранилище в память целиком, а читают его последовательно по байтам либо блокам управляемого размера. Поэтому поток может потреблять мало памяти независимо от размера его опорного хранилища.
Потоки делятся на две категории:
- потоки опорных хранилищ — потоки, жестко привязанные к конкретным типам опорных хранилищ, такие как
FileStream
илиNetworkStream
- потоки-декораторы — наполняют другие потоки, трансформируя данные тем или иным способом, такие как
DeflateStream
илиCryptoStream
Декораторы освобождают потоки опорных хранилищ от необходимости самостоятельно реализовывать такие вещи, как сжатие и шифрование. Декораторы можно подключать во время выполнения, а также соединять их в цепочки (т.е. использовать несколько декораторов в одном потоке).
Потоки работают с байтами. Это гибко и эффективно, но не всегда удобно, например, когда приложение имеет дело с текстом или XML. Преодолеть этот разрыв позволяютадаптеры, они помещают поток в оболочку класса со специальными методами для конкретного формата. В отличие от декораторов адаптеры сами не являются потоками, они обычно полностью скрывают байт-ориентированные методы.
Потоки
Абстрактный класс Stream
является базовым для всех потоков. Он определяет методы и свойства для трех фундаментальных операций: чтение, запись и поиск, а также для выполнения служебных задач: закрытие, сброс, конфигурирование тайм-аутов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // Чтение: public abstract bool CanRead { get; } public abstract int Read (byte[] buffer, int offset, int count) public virtual int ReadByte (); // Запись: public abstract bool CanWrite { get; } public abstract void Write (byte[] buffer, int offset, int count); public virtual void WriteByte (byte value); // Поиск: public abstract bool CanSeek { get; } public abstract long Position { get; set; } public abstract void SetLength (long value); public abstract long Length { get; } public abstract long Seek (long offset, SeekOrigin origin); // Закрытие/сброс: public virtual void Close (); public void Dispose (); public abstract void Flush (); // Тайм-ауты: public virtual bool CanTimeout { get; } public virtual int ReadTimeout { get; set; } public virtual int WriteTimeout { get; set; } // Другие: public static readonly Stream Null; public static Stream Synchronized (Stream stream); |
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | using System; using System.IO; class Program { static void Main() { // CСоздать в текущем каталоге файл test.txt: using (Stream s = new FileStream ("test.txt", FileMode.Create)) { Console.WriteLine (s.CanRead); // True Console.WriteLine (s.CanWrite); // True Console.WriteLine (s.CanSeek); // True s.WriteByte (101); s.WriteByte (102); byte[] block = { 1, 2, 3, 4, 5 }; s.Write (block, 0, block.Length); // Записать блок из 5 байтов Console.WriteLine (s.Length); // 7 Console.WriteLine (s.Position); // 7 s.Position = 0; // Переместиться обратно в начало Console.WriteLine (s.ReadByte()); // 101 Console.WriteLine (s.ReadByte()); // 102 // Читать из потока в массив block: Console.WriteLine (s.Read (block, 0, block.Length)); // 5 Console.WriteLine (s.Read (block, 0, block.Length)); // 0 } } } |
Кроме того класс содержит асинхронные методы для чтения и записи: ReadAsync
иWriteAsync
:
1 2 3 4 5 6 7 8 9 10 11 | async static void AsyncDemo() { using (Stream s = new FileStream ("test.txt", FileMode.Create)) { byte[] block = { 1, 2, 3, 4, 5 }; await s.WriteAsync (block, 0, block.Length); // Асинхронная запись s.Position = 0; // Переместиться в начало // Читать из потока в массив block: Console.WriteLine (await s.ReadAsync (block, 0, block.Length)); // 5 } } |
Чтение и запись
Поток может поддерживать чтение, запись или то и другое. Если свойство CanWrite
возвращает false
— поток предназначен только для чтения, если CanRead
возвращаетfalse
— поток предназначен только для записи.
Метод Read
читает блок данных из потока и записывает их в массив. Он возвращает количество полученных байтов, которое может быть либо равно, либо меньше значения аргумента count
. Если оно меньше count
— это значит, что достигнут конец потока или поток выдает данные порциями меньшего размера (в связи с этим однозначно судить о том, что достигнут конец потока можно только если метод возвращает 0):
1 2 3 4 5 6 | byte[] data = new byte [1000]; // bytesRead в конце всегда будет равен 1000, если только сам поток не короче: int bytesRead = 0; int chunkSize = 1; while (bytesRead < data.Length && chunkSize > 0) bytesRead += chunkSize = s.Read (data, bytesRead, data.Length - bytesRead); |
Метод 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
:
1 2 3 | FileStream fs1 = File.OpenRead ("readme.bin"); // Только для чтения FileStream fs2 = File.OpenWrite (@"c:\temp\writeme.tmp"); // Только для записи FileStream fs3 = File.Create (@"c:\temp\writeme.tmp"); // Чтение и запись |
Методы OpenWrite
и Create
ведут себя по разному если файл уже существует: метод Create
удалит все содержимое существующего файла, а метод OpenWrite
оставит содержимое файла не тронутым, установив позицию потока в ноль (если будет записано меньше байтов, чем существовало в файле, метод оставит смесь старого и нового содержимого).
Можно создать экземпляр FileStream
с помощью конструктора класса FileStream
, которому можно передать имя файла или файловый дескриптор, режимы создания и доступа к файлу, опции для совместного использования, буферизации и безопасности:
1 | var fs = new FileStream ("readwrite.tmp", FileMode.Open); // Чтение и запись |
Класс File
определяет также более удобные статические методы, позволяющие открыть файл и прочитать/записать его содержимое за одни шаг:
File.ReadAllText
— читает целый файл и возвращает его содержимое в виде одной строкиFile.ReadAllLines
— читает целый файл и возвращает его содержимое в виде массива строкFile.ReadAllBytes
— читает целый файл и возвращает его содержимое в виде байтового массиваFile.WriteAllText
— записывает строку в файлFile.WriteAllLines
— записывает массив строк в файлFile.WriteAllBytes
— записывает байтовый массив в файлFile.AppendAllText
— добавляет строку в конец файла
Метод File.ReadLines
не загружает весь файл в память, а возвращает лениво-оцениваемое IEnumerable<string>
:
1 | int longLines = File.ReadLines ("filePath").Count (l => l.Length > 80); |
Имя файла
Имя файла может быть абсолютным или относительным к текущему каталогу. Узнать текущий каталог или изменить его можно с помощью статического свойстваEnvironment.CurrentDirectory
. Текущий каталог может как совпадать, так и не совпадать с директорией, в которой расположен исполняемый файл программы.
Свойство AppDomain.CurrentDomain.BaseDirectory
возвращает базовый каталог приложения, который как правило совпадает с директорией, содержащей исполняемый файл программы.
Чтобы указать имя файла относительно полученного каталога, можно использовать метод Path.Combine
:
1 2 3 | string baseFolder = AppDomain.CurrentDomain.BaseDirectory; string logoPath = Path.Combine (baseFolder, "logo.jpg"); Console.WriteLine (File.Exists (logoPath)); |
Допустимо использовать сетевые имена, такие как \\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
:
1 2 | [Flags] public enum FileAccess { Read = 1, Write = 2, ReadWrite = 3 } |
При создании экземпляра 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
можно блокировать и разблокировать участки файла:
1 2 | public virtual void Lock (long position, long length); public virtual void Unlock (long position, long length); |
Если часть или вся запрошенная область файла уже заблокированы метод Lock
выбросит исключение.
MemoryStream
Класс MemoryStream
в качестве опорного хранилища использует массив (целиком расположенный в памяти).
С помощью методов ToArray
и GetBuffer
можно преобразовать MemoryStream
в байтовый массив.
Закрытие и сброс MemoryStream
являются не обязательными. После закрытия выполнять чтение и запись потока нельзя. Метод Flush
потока MemoryStream
вообще ничего не делает.
PipeStream
Класс PipeStream
предоставляет простой способ взаимодействия одного процесса с другим через протокол каналов Windows. Канал — это низкоуровневая конструкция, которая позволяет отправлять и получать байты (или сообщения — группы байтов). Различают два вида каналов:
- анонимный канал — обеспечивает однонаправленное взаимодействие между родительским и дочерним процессом на одном компьютере
- именованный канал — обеспечивает двунаправленное взаимодействие между произвольными процессами на одном компьютере или на разных (через сеть)
PipeStream
является абстрактным классом с четырьмя конкретными подтипами: два применяются для анонимных каналов (AnonymousPipeServerStream
иAnonymousPipeClientStream
), два для именованных (NamedPipeServerStream
и NamedPipeClientStream
).
Именованные потоки
В случае именованных потоков участники взаимодействуют через канал с одним и тем же именем. Протокол определяет две отдельные роли: клиент и сервер, взаимодействие между которыми происходит следующим образом:
- сервер создает экземпляр
NamedPipeServerStream
и вызывает его методWaitForConnection
- клиент создает экземпляр
NamedPipeClientStream
и вызывает его методConnect
(с необязательным тайм-аутом)
После этого участники для взаимодействия осуществляют чтение и запись в потоки:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Сервер: using (var s = new NamedPipeServerStream ("pipedream")) { s.WaitForConnection(); s.WriteByte (100); Console.WriteLine (s.ReadByte()); } // Клиент: using (var s = new NamedPipeClientStream ("pipedream")) { s.Connect(); Console.WriteLine (s.ReadByte()); s.WriteByte (200); } |
Для поддержки сообщений длиннее одного байта каналы предлагают режим передачи сообщений. Когда он включен, участник читающий поток, может узнать, что сообщение завершено, проверив свойство потока IsMessageComplete
. Включить режим передачи сообщений на стороне сервера можно указав PipeTransmissionMode.Message
при создании экземпляра потока, а на стороне клиента — присвоив значение PipeTransmissionMode.Message
свойству потока ReadMode
после вызова Connect
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // Сервер: using (var s = new NamedPipeServerStream ("pipedream", PipeDirection.InOut, 1, PipeTransmissionMode.Message)) { s.WaitForConnection(); byte[] msg = Encoding.UTF8.GetBytes ("Hello"); s.Write (msg, 0, msg.Length); Console.WriteLine (Encoding.UTF8.GetString (ReadMessage (s))); } // Клиент: using (var s = new NamedPipeClientStream ("pipedream")) { s.Connect(); s.ReadMode = PipeTransmissionMode.Message; Console.WriteLine (Encoding.UTF8.GetString (ReadMessage (s))); byte[] msg = Encoding.UTF8.GetBytes ("Hello right back!"); s.Write (msg, 0, msg.Length); } // Метод для чтения потока на клиенте: static byte[] ReadMessage (PipeStream s) { MemoryStream ms = new MemoryStream(); byte[] buffer = new byte [0x1000]; do { ms.Write (buffer, 0, s.Read (buffer, 0, buffer.Length)); } while (!s.IsMessageComplete); return ms.ToArray(); } |
Определить, завершил ли поток PipeStream
чтение сообщения, нельзя за счет простого ожидания, когда Read
вернет 0, т.к. потоки каналов и сетевые потоки не имеют определенного окончания, а между передачами сообщения они опустошаются.
Клиент и сервер должны следовать определенному протоколу для координации своих действий, чтобы оба участника не начали одновременно принимать или отправлять данные.
Анонимные потоки
Анонимные каналы вместо имени используют закрытый дескриптор. Они также предполагают две роли: клиент и сервер, но взаимодействие осуществляется иначе:
- сервер создает экземпляр
AnonymousPipeServerStream
и устанавливает его направление через свойствоPipeDirection
, которое может бытьIn
илиOut
- сервер вызывает метод
GetClientHandleAsString
для получения дескриптора канала, который затем передается клиенту (обычно как аргумент при запуске дочернего процесса) - клиент создает экземпляр
AnonymousPipeClientStream
, указывая ему противоположное направление (свойствоPipeDirection
) - родительский и дочерний процессы взаимодействуют посредством чтения и записи в потоки
- сервер освобождает локальный дескриптор за счет вызова метода
DisposeLocalCopyOfClientHandle
Поскольку анонимные каналы являются однонаправленными, для реализации двунаправленного взаимодействия сервер должен создать два канала.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // Сервер: string clientExe = @"d:\PipeDemo\ClientDemo.exe"; HandleInheritability inherit = HandleInheritability.Inheritable; using (var tx = new AnonymousPipeServerStream (PipeDirection.Out, inherit)) using (var rx = new AnonymousPipeServerStream (PipeDirection.In, inherit)) { string txID = tx.GetClientHandleAsString(); string rxID = rx.GetClientHandleAsString(); var startInfo = new ProcessStartInfo (clientExe, txID + " " + rxID); startInfo.UseShellExecute = false; // Требуется для дочернего процесса Process p = Process.Start (startInfo); tx.DisposeLocalCopyOfClientHandle(); rx.DisposeLocalCopyOfClientHandle(); tx.WriteByte (100); Console.WriteLine ("Server received: " + rx.ReadByte()); p.WaitForExit(); } // Клиент: string rxID = args[0]; string txID = args[1]; using (var rx = new AnonymousPipeClientStream (PipeDirection.In, rxID)) using (var tx = new AnonymousPipeClientStream (PipeDirection.Out, txID)) { Console.WriteLine ("Client received: " + rx.ReadByte()); tx.WriteByte (200); } |
Клиент и сервер в случае анонимных каналов также должны координировать свои действия по отправке и получению данных, а также согласовать длину каждой передачи (анонимные каналы не поддерживают режим передачи сообщений).
BufferedStream
Класс BufferedStream
является декоратором, он помещает другой поток в оболочку и добавляет ему возможность буферизации. Буферизация улучшает производительность, сокращая количество двусторонних обменов с опорным хранилищем.
1 2 3 4 5 6 7 8 | // Записать 100K в файл: File.WriteAllBytes ("myFile.bin", new byte [100000]); using (FileStream fs = File.OpenRead ("myFile.bin")) using (BufferedStream bs = new BufferedStream (fs, 20000)) // буфер размером 20K { bs.ReadByte(); Console.WriteLine (fs.Position); // 20000 } |
В примере поток FileStream
помещается в декоратор BufferedStream
с буфером в 20Kb. При вызова ReadByte
фактически из потока FileStream
считывается не один байт, а 20 000 байтов (размер буфера), которые помещаются в буфер. При следующих 19 999 вызовах метода ReadByte
данные будут читаться из буфера, а не из файлового потока. На практике соединение FileStream
с BufferedStream
избыточно, так как FileStream
сам поддерживает буферизацию.
Закрытие BufferedStream
автоматически закрывает лежащий в основе поток с опорным хранилищем.
Текстовые адаптеры
TextReader и TextWriter
TextReader
и TextWriter
являются абстрактными базовыми классами для адаптеров, которые имеют дело исключительно с символами и строками. Каждый из них имеет две реализации:
StreamReader
иStreamWriter
— для хранения данных используют классStream
и транслируют байты потока в символы или строкиStringReader
иStringWriter
— для хранения данных используют строки
Члены класса TextReader
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Чтение одного символа: public virtual int Peek (); // Результат приводится к char public virtual int Read (); // Результат приводится к char // Чтение нескольких символов: public virtual int Read (char[] buffer, int index, int count); public virtual int ReadBlock (char[] buffer, int index, int count); public virtual string ReadLine (); public virtual string ReadToEnd (); // Закрытие: public virtual void Close (); public void Dispose (); // Другие: public static readonly TextReader Null; public static TextReader Synchronized (TextReader reader); |
Метод Peek
возвращает следующий символ из потока, не перемещая текущую позицию вперед. Метод Peek
и Read
(без аргументов) возвращают -1
при достижении конца потока или целочисленное значение, которое может быть приведено к char
.
Перегруженная версия Read
, принимающая буфер char[]
, идентична по функционалу методу ReadBlock
.
Метод ReadLine
выполняет чтение до тех пор, пока не встретит символы новой строки (CR, LF либо CR+LF). Он возвращает строку отбрасывая символы новой строки.
Члены класса TextWriter
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // Запись одного символа: public virtual void Write (char value); // Запись нескольких символов: public virtual void Write (string value); public virtual void Write (char[] buffer, int index, int count); public virtual void Write (string format, params object[] arg); public virtual void WriteLine (string value); // Закрытие и сброс: public virtual void Close (); public void Dispose (); public virtual void Flush (); // Форматирование и кодировка: public virtual IFormatProvider FormatProvider { get; } public virtual string NewLine { get; set; } public abstract Encoding Encoding { get; } // Другие: public static readonly TextWriter Null; public static TextWriter Synchronized (TextWriter writer); |
Методы Write
и WriteLine
дополнительно перегружены, чтобы принимать каждый из примитивных типов, а также тип object
(они просто вызывают ToString
на том, что им передано).
Метод WriteLine
дополняет переданную ему строку последовательностью CR+LF. Свойство NewLine
позволяет заменить эту последовательность на другие символы.
Классы TextReader
и TextWriter
также определяют асинхронные версии методов чтения и записи.
StreamReader и StreamWriter
Сами по себе классы TextReader
и TextWriter
являются абстрактными и никак не связаны ни с потоком, ни с опорным хранилищем. А вот реализующие их типы StreamReader
и StreamWriter
имеют в своей основе байтовый поток и выполняют преобразование между символами и байтами.
Для создания экземпляра StreamReader
или StreamWriter
их конструкторам необходимо передать байтовый поток:
1 2 3 4 5 6 7 8 9 10 11 12 | using (FileStream fs = File.Create ("test.txt")) using (TextWriter writer = new StreamWriter (fs)) { writer.WriteLine ("Line1"); writer.WriteLine ("Line2"); } using (FileStream fs = File.OpenRead ("test.txt")) using (TextReader reader = new StreamReader (fs)) { Console.WriteLine (reader.ReadLine()); Console.WriteLine (reader.ReadLine()); } |
Класс File предоставляет статические методы CreateText
, AppendText
и OpenText
, которые позволяют создать экземпляр StreamReader
или StreamWriter
более лаконично:
1 2 3 4 5 6 7 8 9 10 | using (TextWriter writer = File.CreateText ("test.txt")) { writer.WriteLine ("Line1"); writer.WriteLine ("Line2"); } using (TextWriter writer = File.AppendText ("test.txt")) writer.WriteLine ("Line3"); using (TextReader reader = File.OpenText ("test.txt")) while (reader.Peek() > −1) Console.WriteLine (reader.ReadLine()); |
Достижение конца файла можно проверить с помощью метода Peek
(вернет -1
) или метода ReadLine
(вернет null
).
Преобразование байтов потока в строку выполняется с помощью класса System.Text.Encoding
, который можно передать в конструктор StreamReader
или StreamWriter
. По умолчанию используется кодировка UTF-8.
1 2 3 | using (Stream s = File.Create ("but.txt")) using (TextWriter w = new StreamWriter (s, Encoding.Unicode)) w.WriteLine ("but-"); |
StringReader и StringWriter
Адаптеры StringReader
и StringWriter
вообще не имеют дела с потоками. Вместо этого они используют в качестве лежащего в основе источника данных строку или экземпляр StringBuilder
. Они не выполняют никаких преобразований байтов в строку и обратно, по сути они не делают ничего такого, что нельзя было бы сделать с помощью строк и StringBuilder
. Преимущество же их в том, что они имеют общий базовый класс с типами StreamReader
и StreamWriter
, облегчая их преобразования.
Двоичные адаптеры
Классы BinaryReader
и BinaryWriter
выполняют чтение и запись в поток предопределенных типов: bool
, byte
, char
, decimal
, float
, double
, short
, int
,long
, sbyte
, ushort
, uint
и ulong
, а также строк и массивов предопределенных типов. В отличие от текстовых адаптеров двоичные адаптеры сохраняют предопределенные символы эффективнее, так как они представлены в памяти.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class Person { public string Name; public int Age; public double Height; public void SaveData (Stream s) { var w = new BinaryWriter (s); w.Write (Name); w.Write (Age); w.Write (Height); w.Flush(); } public void LoadData (Stream s) { var r = new BinaryReader (s); Name = r.ReadString(); Age = r.ReadInt32(); Height = r.ReadDouble(); } } |
Класс BinaryReader
может также выполнять чтение в байтовый массив:
1 | byte[] data = new BinaryReader(s).ReadBytes ((int) s.Length); |
Закрытие адаптеров потока
Закрытие адаптера приводит к автоматическому закрытию лежащего в основе потока:
1 2 3 | using (FileStream fs = File.Create ("test.txt")) using (TextWriter writer = new StreamWriter (fs)) writer.WriteLine ("Line"); |
При этом если в конструкторе адаптера будет выброшено исключение, поток все равно закроется.
При закрытии адаптера и потока без использования инструкции using, нужно всегда сначала закрывать или сбрасывать адаптер, и только после этого закрывать поток, иначе любые данные, буферизированные в адаптере, будут утеряны.
Адаптеры относятся к необязательно освобождаемым объектам. Это означает, что их необязательно закрывать перед закрытием потока. В большинстве случаев их достаточно просто сбросить. Это может быть удобно в ситуации, когда после завершения работы с адаптером лежащий в его основе поток должен остаться для дальнейшей работы с ним:
1 2 3 4 5 6 7 8 | using (FileStream fs = new FileStream ("test.txt", FileMode.Create)) { StreamWriter writer = new StreamWriter (fs); writer.WriteLine ("Hello"); writer.Flush(); fs.Position = 0; Console.WriteLine (fs.ReadByte()); } |
В примере если вместо сброса адаптера закрыть его, лежащий в его основе поток тоже будет закрыт, что приведет к сбою последующих операций над потоком.
Если конструкторы адаптера StreamReader
/StreamWriter
в качестве четвертого параметра передать true
, то после его освобождения лежащий в основе поток закрыт не будет:
1 2 3 4 5 6 7 8 9 10 11 | using (var fs = new FileStream ("test.txt", FileMode.Create)) { using (var writer = new StreamWriter (fs, new UTF8Encoding (false, true), 0x400, true)) writer.WriteLine ("Hello"); fs.Position = 0; Console.WriteLine (fs.ReadByte()); Console.WriteLine (fs.Length); } |
Сжатие потоков
В пространстве имен System.IO.Compression
доступно два декоратора для сжатия потоков: DeflateStream
и GZipStream
. Отличаются они тем, что GZipStream
записывает дополнительную информацию в начале и в конце, а также соответствует стандарту. Оба класса являются декораторами: они сжимают или распаковывают данные из другого потока, который указывается при создании их экземпляра:
1 2 3 4 5 6 7 8 | using (Stream s = File.Create ("compressed.bin")) using (Stream ds = new DeflateStream (s, CompressionMode.Compress)) for (byte i = 0; i < 100; i++) ds.WriteByte (i); using (Stream s = File.OpenRead ("compressed.bin")) using (Stream ds = new DeflateStream (s, CompressionMode.Decompress)) for (byte i = 0; i < 100; i++) Console.WriteLine (ds.ReadByte()); |
В этом же пространстве имен определяются еще два класса: ZipArchive
и ZipFile
, которые использую популярный алгоритм сжатия, применяемый в zip-файлах, что делает их совместимыми с zip-файлами, созданными в других приложениях, а также позволяет сжимать несколько файлов в один архив.
Класс ZipArchive
работает непосредственно с потоками, а ZipFile
является статическим вспомогательным классом для него. ZipFile
более удобен в использовании при работе с файлами.
Метод CreateFromDirectory
класса ZipFile
добавляет все файлы из указанного каталога в zip-архив:
1 | ZipFile.CreateFromDirectory (@"d:\MyFolder", @"d:\compressed.zip"); |
Метод ExtractToDirectory
того же класса извлекает содержимое zip-архива в указанный каталог:
1 | ZipFile.ExtractToDirectory (@"d:\compressed.zip", @"d:\MyFolder"); |
При сжатии можно задать оптимизацию по размеру файла или по скорости сжатия, а также необходимость включения в архив исходных директории.
Экземпляр ZipArchive
можно создать либо с помощью конструктора, передав ему поток (объект Stream
), либо с помощью статического метода Open
класс ZipFile
, передав ему имя файла и действие, которое должно быть произведено над архивом —Read
(чтение), Create
(создание) или Update
(обновление). Свойство Entries
объекта ZipArchive
возвращает коллекцию входящих в архив файлов, а метод GetEntry
позволяет найти конкретный файл:
1 2 3 | using (ZipArchive zip = ZipFile.Open (@"d:\zz.zip", ZipArchiveMode.Read)) foreach (ZipArchiveEntry entry in zip.Entries) Console.WriteLine (entry.FullName + " " + entry.Length); |
Класс ZipArchiveEntry
инкапсулирует отдельный файл в архиве. Он имеет методыDelete
(позволяет удалить файл из архива), ExtractToFile
(позволяет извлечь файл из архива) и Open
(возвращает экземпляр Stream
, с возможностью чтения/записи). Создавать новые файлы в архиве можно с помощью методов CreateEntry
иCreateEntryFromFile
класса ZipArchive
.
1 2 3 | byte[] data = File.ReadAllBytes (@"d:\foo.dll"); using (ZipArchive zip = ZipFile.Open (@"d:\zz.zip", ZipArchiveMode.Update)) zip.CreateEntry (@"bin\X64\foo.dll").Open().Write (data, 0, data.Length); |
Манипулирование файлами и каталогами
Пространство имен System.IO
содержит ряд типов, предназначенных для манипулирования файлами и каталогами, позволяющих копировать, перемещать, создавать файлы и каталоги, устанавливать их атрибуты и права доступа. Сюда входят статические классы File
и Directory
, экземплярные классы FileInfo
иDirectoryInfo
, а также статический класс Path
, позволяющий манипулировать строками путей к файлам и каталогам.
Класс File
File
— статический класс, все методы которого принимают имя файла. Имя файла может быть абсолютным или относительным (относительно текущего каталога). Класс включает следующие статические методы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | bool Exists (string path); // Возвращает true если файл существует void Delete (string path); void Copy (string sourceFileName, string destFileName); void Move (string sourceFileName, string destFileName); void Replace (string sourceFileName, string destinationFileName, string destinationBackupFileName); FileAttributes GetAttributes (string path); void SetAttributes (string path, FileAttributes fileAttributes); void Decrypt (string path); void Encrypt (string path); DateTime GetCreationTime (string path); DateTime GetLastAccessTime (string path); DateTime GetLastWriteTime (string path); void SetCreationTime (string path, DateTime creationTime); void SetLastAccessTime (string path, DateTime lastAccessTime); void SetLastWriteTime (string path, DateTime lastWriteTime); FileSecurity GetAccessControl (string path); FileSecurity GetAccessControl (string path, AccessControlSections includeSections); void SetAccessControl (string path, FileSecurity fileSecurity); |
Методы Move
и Replace
позволяют переименовать файл или переместить его в другой каталог, при этом Move генерирует исключение, если файл назначения существует, а Replace
этого не делает.
Метод Delete
удаляет файл, а если файл помечен как предназначенный только для чтения метод сгенерирует исключение UnauthorizedAccessException
.
Метод GetAttributes
возвращает enum FileAttribute
со следующими значениями, которые можно комбинировать:
Archive
Compressed
Device
Directory
Encrypted
Hidden
Normal
NotContentIndexed
Offline
ReadOnly
ReparsePoint
SparseFile
System
Temporary
С помощью метода SetAttributes
атрибуты файла можно менять:
1 2 3 4 5 6 7 8 9 | string filePath = @"c:\temp\test.txt"; FileAttributes fa = File.GetAttributes (filePath); if ((fa & FileAttributes.ReadOnly) > 0) { fa ^= FileAttributes.ReadOnly; File.SetAttributes (filePath, fa); } // теперь файл можно, например, удалить File.Delete (filePath); |
С помощью класса FileInfo
это можно сделать лаконичней:
1 | new FileInfo (@"c:\temp\test.txt").IsReadOnly = false; |
Атрибуты Compressed
и Encrypted
(сжатие и шифрование) с помощью методаSetAttribute
изменить нельзя. Для шифрования и дешифрования предназначены методы Encrypt
и Decrypt
класса File
. Для сжатия класс File
методов не содержит.
Методы GetAccessControl
и SetAccessControl
позволяют получать и задавать права доступа ОС через объект FileSecurity
(пространство имен System.Security.AccessControl
). Этот объект также можно передать в конструктор FileStream
для указания прав доступа при создании файла.
Класс Directory
Статический класс Directory
содержит методы аналогичные методам классаFile
: для проверки существования каталога (Exists
), перемещения каталога (Move
), удаления каталога (Delete
), получения/установки времени создания и последнего доступа, получения/установки прав доступа. Кроме того Directory
включает следующие статические методы:
1 2 3 4 5 6 7 8 9 10 11 12 13 | string GetCurrentDirectory (); void SetCurrentDirectory (string path); DirectoryInfo CreateDirectory (string path); DirectoryInfo GetParent (string path); string GetDirectoryRoot (string path); string[] GetLogicalDrives (); // Все следующие методы возвращают полный путь: string[] GetFiles (string path); string[] GetDirectories (string path); string[] GetFileSystemEntries (string path); IEnumerable<string> EnumerateFiles (string path); IEnumerable<string> EnumerateDirectories (string path); IEnumerable<string> EnumerateFileSystemEntries (string path); |
Методы Enumerate*
эффективней методов Get*
, поскольку извлекают данные только при перечислении. И те и другие перегружены и могут принимать searchPattern
(string
) и searchOption
(enum
). При указании SearchOption
.SearchAllSubDirectories
также будет выполняться рекурсивный поиск в подкаталогах. Методы *FileSystemEntries
сочетают в себе функционал методов *Files
и *Directories
.
FileInfo и DirectoryInfo
Если над файлом или каталогом требуется выполнить последовательность операций, то более удобными будут экземплярные методы FileInfo
и DirectoryInfo
.
Класс FileInfo
предлагает большинство статических методов класс File
в форме экземплярных методов, а также содержит ряд дополнительных свойств — Extension
,Length
, IsReadOnly
и Directory
. Последний возвращает объект DirectoryInfo
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | FileInfo fi = new FileInfo (@"c:\temp\FileInfo.txt"); Console.WriteLine (fi.Exists); // false using (TextWriter w = fi.CreateText()) w.Write ("Some text"); Console.WriteLine (fi.Exists); // по прежнему false fi.Refresh(); Console.WriteLine (fi.Exists); // true Console.WriteLine (fi.Name); // FileInfo.txt Console.WriteLine (fi.FullName); // c:\temp\FileInfo.txt Console.WriteLine (fi.DirectoryName); // c:\temp Console.WriteLine (fi.Directory.Name); // temp Console.WriteLine (fi.Extension); // .txt Console.WriteLine (fi.Length); // 9 fi.Encrypt(); fi.Attributes ^= FileAttributes.Hidden; // Переключает флаг "скрытый" fi.IsReadOnly = true; Console.WriteLine (fi.Attributes); // ReadOnly, Archive, Hidden, Encrypted Console.WriteLine (fi.CreationTime); fi.MoveTo (@"c:\temp\FileInfoX.txt"); DirectoryInfo di = fi.Directory; Console.WriteLine (di.Name); // temp Console.WriteLine (di.FullName); // c:\temp Console.WriteLine (di.Parent.FullName); // c:\ di.CreateSubdirectory ("SubFolder"); |
Пример использования класса DirectoryInfo:
1 2 3 4 5 | DirectoryInfo di = new DirectoryInfo (@"e:\photos"); foreach (FileInfo fi in di.GetFiles ("*.jpg")) Console.WriteLine (fi.Name); foreach (DirectoryInfo subDir in di.GetDirectories()) Console.WriteLine (subDir.FullName); |
Path
Статический класс Path
содержит методы и поля для работы с путями и именами файлов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | string dir = @"c:\mydir"; string file = "myfile.txt"; string path = @"c:\mydir\myfile.txt"; Directory.SetCurrentDirectory (@"k:\demo"); // Методы Path: Path.IsPathRooted (file) // False Path.IsPathRooted (path) // True Path.GetPathRoot (path) // c:\ Path.GetDirectoryName (path) // c:\mydir Path.GetFileName (path) // myfile.txt Path.GetFullPath (file) // k:\demo\myfile.txt Path.Combine (dir, file) // c:\mydir\myfile.txt // Расширения файлов: Path.HasExtension (file) // True Path.GetExtension (file) // .txt Path.GetFileNameWithoutExtension (file) // myfile Path.ChangeExtension (file, ".log") // myfile.log // Разделители и символы: Path.AltDirectorySeparatorChar // / Path.PathSeparator // ; Path.VolumeSeparatorChar // : Path.GetInvalidPathChars() // символы от 0 до 31 и "<>| Path.GetInvalidFileNameChars() // символы от 0 до 31 и "<>|:*?\/ // Временные файлы: Path.GetTempPath() // <local user folder>\Temp Path.GetRandomFileName() // d2dwuzjf.dnp Path.GetTempFileName() // <local user folder>\Temp\tmp14B.tmp |
Метод Combine
объединяет каталог и имя файла, при необходимости дополняя имя каталога косой чертой. Метод GetFullPath
преобразует относительный путь в абсолютный. Метод GetRandomFileName
возвращает уникальное случайное имя файла в формате 8.3, не создавая при этом файл. Метод GetTempFileName
генерирует временное имя файла с применением автоинкрементного счетчика и затем создает пустой файл с этим именем в локальном временном каталоге.
Специальные директории
В перечисленных выше классах отсутствуют средства нахождения специальных директорий Windows, таких как My Documents, Program Files и др. Сделать это можно с помощью метода GetFolderPath
класса System.Environment
:
1 2 | string myDocPath = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments); |
Значения 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | DriveInfo c = new DriveInfo ("C"); // Запросить диск C: long totalSize = c.TotalSize; // Размер в байтах long freeBytes = c.TotalFreeSpace; // Игнорирует дисковую квоту long freeToMe = c.AvailableFreeSpace; // Учитывает дисковую квоту foreach (DriveInfo d in DriveInfo.GetDrives()) // Все устройства хранения данных { Console.WriteLine (d.Name); // C:\ Console.WriteLine (d.DriveType); // Жесткий диск Console.WriteLine (d.RootDirectory); // C:\ if (d.IsReady) // Если устройство не готово следующие свойства выбросят исключение { Console.WriteLine (d.VolumeLabel); // System Console.WriteLine (d.DriveFormat); // NTFS } } |
enum DriveType
содержит следующие значения:
Unknown
NoRootDirectory
Removable
Fixed
Network
CDRom
Ram
Перехват событий файловой системы
Класс FileSystemWatcher
позволяет отслеживать действия над каталогами и их подкаталогами: создание, модификация, переименование, удаление файлов и поддиректорий, а также изменение их атрибутов. Событие генерируется независимо от того, кем оно совершено — пользователем или процессом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | static void Main() { Watch (@"c:\temp", "*.txt", true); } static void Watch (string path, string filter, bool includeSubDirs) { using (var watcher = new FileSystemWatcher (path, filter)) { watcher.Created += FileCreatedChangedDeleted; watcher.Changed += FileCreatedChangedDeleted; watcher.Deleted += FileCreatedChangedDeleted; watcher.Renamed += FileRenamed; watcher.Error += FileError; watcher.IncludeSubdirectories = includeSubDirs; watcher.EnableRaisingEvents = true; Console.WriteLine ("Listening for events - press <enter> to end"); Console.ReadLine(); } } static void FileCreatedChangedDeleted (object o, FileSystemEventArgs e) { Console.WriteLine ("File {0} has been {1}", e.FullPath, e.ChangeType); } static void FileRenamed (object o, RenamedEventArgs e) { Console.WriteLine ("Renamed: {0}->{1}", e.OldFullPath, e.FullPath); } static void FileError (object o, ErrorEventArgs e) { Console.WriteLine ("Error: " + e.GetException().Message); } |
Размещенные в памяти файлы (Memory-Mapped Files)
Типы для размещенных в памяти файлов находятся в пространстве имен System.IO.MemoryMappedFiles
. Размещенные в памяти файлы дают две ключевые возможности:
- эффективный произвольный доступ к файловым данным
- возможность разделения памяти между различными процессами на одном компьютере
Хотя класс FileStream
дает возможность произвольного ввода-вывода, он оптимизирован для последовательного ввода-вывода. Как правило он в 10 раз быстрее при последовательном вводе-выводе и в 10 раз медленнее при произвольном чем файлы размещенные в памяти.
Чтобы создать размещенный в памяти файл необходимо:
- получить объект
FileStreams
- создать экземпляр
MemoryMappedFile
, передав конструктору файловый поток - вызвать метод
CreateViewAccessor
на объектеMemoryMappedFile
В результате последнего действия будет получен объект MemoryMappedViewAccessor
, предоставляющий методы для произвольного чтения и записи простых типов, структур и массивов.
1 2 3 4 5 6 7 | File.WriteAllBytes ("long.bin", new byte [1000000]); using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile ("long.bin")) using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor()) { accessor.Write (500000, (byte) 77); Console.WriteLine (accessor.ReadByte (500000)); // 77 } |
Методу CreateFromFile
можно также передать имя размещенного в памяти файла и емкость.
1 2 | using (var mmf = MemoryMappedFile.CreateFromFile ("long.bin", FileMode.Create, null, 1000)) |
Указание имени отличного от null
позволяет разделять блок памяти с другими процессами. Хотя обычно для этого используется другой метод — CreateNew
: один из процессов создает блок разделяемой памяти, вызывая MemoryMappedFile.CreateNew
и передает ему имя создаваемого размещенного в памяти файла, другой процесс подписывается на этот блок памяти, вызывая метод MemoryMappedFile.OpenExisting
с таким же именем.
1 2 3 4 5 6 7 8 9 10 11 | // Первый процесс: using (MemoryMappedFile mmFile = MemoryMappedFile.CreateNew ("Demo", 500)) using (MemoryMappedViewAccessor accessor = mmFile.CreateViewAccessor()) { accessor.Write (0, 12345); Console.ReadLine(); } // Второй процесс: using (MemoryMappedFile mmFile = MemoryMappedFile.OpenExisting ("Demo")) using (MemoryMappedViewAccessor accessor = mmFile.CreateViewAccessor()) Console.WriteLine (accessor.ReadInt32 (0)); // 12345 |
Методы аксессора представления
Метод CreateViewAccessor
объекта MemoryMappedFile
создает аксессор представления — объект MemoryMappedViewAccessor
, позволяющий выполнять чтение и запись в произвольных позициях.
Методы Read*
/Write*
принимают числовые типы, bool
, char
, массивы и структуры (содержащие элементы и поля значимых типов). Ссылочные типы и их массивы/структуры запрещены. Поэтому чтобы записать строку ее нужно закодировать в байтовый массив:
1 2 3 4 5 6 7 8 | // Запись строки byte[] data = Encoding.UTF8.GetBytes ("This is a test"); accessor.Write (0, data.Length); accessor.WriteArray (4, data, 0, data.Length); // Чтение строки byte[] data = new byte [accessor.ReadInt32 (0)]; accessor.ReadArray (4, data, 0, data.Length); Console.WriteLine (Encoding.UTF8.GetString (data)); // This is a test |
Изолированное хранилище
Каждая программа .NET имеет доступ к локальной области хранения, уникальной для этой программы, которая называется изолированным хранилищем. Это хранилище удобно, когда программа не имеет доступа к стандартной файловой системе (приложения Silverlight и некоторые интернет приложения), но его использование обладает рядом недостатков (неудобство работы, большие ограничения).
Чтобы получить поток изолированного хранилища, сначала нужно указать требуемый тип изоляции, вызвав один из статических методов класса IsolatedStorageFile
:
GetUserStoreForDomain
GetMachineStoreForDomain
GetUserStoreForAssembly
GetMachineStoreForAssembly
Затем он используется для создания объекта IsolatedStorageFileStream
:
1 2 3 4 5 6 7 8 | using (IsolatedStorageFile f = IsolatedStorageFile.GetMachineStoreForDomain()) using (var s = new IsolatedStorageFileStream ("hi.txt",FileMode.Create,f)) using (var writer = new StreamWriter (s)) writer.WriteLine ("Hello, World"); using (IsolatedStorageFile f = IsolatedStorageFile.GetMachineStoreForDomain()) using (var s = new IsolatedStorageFileStream ("hi.txt", FileMode.Open, f)) using (var reader = new StreamReader (s)) Console.WriteLine (reader.ReadToEnd()); |
IsolatedStorageFile
вызвав статический метод IsolatedStorageFile.GetStore
, передав ему правильную комбинацию флагов StorageScope
:1 2 3 4 5 6 7 | var flags = IsolatedStorageScope.Machine | IsolatedStorageScope.Application | IsolatedStorageScope.Assembly; using (IsolatedStorageFile f = IsolatedStorageFile.GetStore (flags, typeof (StrongName), typeof (StrongName))) { ... |