Zip Files in Csharp
Zip Files in Csharp
Zip Files in Csharp
File and stream I/O (input/output) refers to the transfer of data either to or from a storage medium. In the .NET
Framework, the System.IO namespaces contain types that enable reading and writing, both synchronously and
asynchronously, on data streams and files. These namespaces also contain types that perform compression and
decompression on files, and types that enable communication through pipes and serial ports.
A file is an ordered and named collection of bytes that has persistent storage. When you work with files, you
work with directory paths, disk storage, and file and directory names. In contrast, a stream is a sequence of bytes
that you can use to read from and write to a backing store, which can be one of several storage mediums (for
example, disks or memory). Just as there are several backing stores other than disks, there are several kinds of
streams other than file streams, such as network, memory, and pipe streams.
Streams
The abstract base class Stream supports reading and writing bytes. All classes that represent streams inherit
from the Stream class. The Stream class and its derived classes provide a common view of data sources and
repositories, and isolate the programmer from the specific details of the operating system and underlying
devices.
Streams involve three fundamental operations:
Reading - transferring data from a stream into a data structure, such as an array of bytes.
Writing - transferring data to a stream from a data source.
Seeking - querying and modifying the current position within a stream.
Depending on the underlying data source or repository, a stream might support only some of these capabilities.
For example, the PipeStream class does not support seeking. The CanRead, CanWrite, and CanSeek properties
of a stream specify the operations that the stream supports.
Here are some commonly used stream classes:
FileStream – for reading and writing to a file.
IsolatedStorageFileStream – for reading and writing to a file in isolated storage.
MemoryStream – for reading and writing to memory as the backing store.
BufferedStream – for improving performance of read and write operations.
NetworkStream – for reading and writing over network sockets.
PipeStream – for reading and writing over anonymous and named pipes.
CryptoStream – for linking data streams to cryptographic transformations.
For an example of working with streams asynchronously, see Asynchronous File I/O.
Compression
Compression refers to the process of reducing the size of a file for storage. Decompression is the process of
extracting the contents of a compressed file so they are in a usable format. The System.IO.Compression
namespace contains types for compressing and decompressing files and streams.
The following classes are frequently used when compressing and decompressing files and streams:
ZipArchive – for creating and retrieving entries in the zip archive.
ZipArchiveEntry – for representing a compressed file.
ZipFile – for creating, extracting, and opening a compressed package.
ZipFileExtensions – for creating and extracting entries in a compressed package.
DeflateStream – for compressing and decompressing streams using the Deflate algorithm.
GZipStream – for compressing and decompressing streams in gzip data format.
See How to: Compress and Extract Files.
Isolated storage
Isolated storage is a data storage mechanism that provides isolation and safety by defining standardized ways of
associating code with saved data. The storage provides a virtual file system that is isolated by user, assembly, and
(optionally) domain. Isolated storage is particularly useful when your application does not have permission to
access user files. You can save settings or files for your application in a manner that is controlled by the
computer's security policy.
Isolated storage is not available for Windows 8.x Store apps; instead, use application data classes in the
Windows.Storage namespace. For more information, see Application data.
The following classes are frequently used when implementing isolated storage:
IsolatedStorage – provides the base class for isolated storage implementations.
IsolatedStorageFile – provides an isolated storage area that contains files and directories.
IsolatedStorageFileStream - exposes a file within isolated storage.
See Isolated Storage.
Related topics
Common I/O Tasks
Provides a list of I/O tasks associated with files, directories, and streams, and links to relevant content and
examples for each task.
Asynchronous File I/O
Describes the performance advantages and basic operation of asynchronous I/O.
Isolated Storage
Describes a data storage mechanism that provides isolation and safety by defining standardized ways of
associating code with saved data.
Pipes
Describes anonymous and named pipe operations in the .NET Framework.
Memory-Mapped Files
Describes memory-mapped files, which contain the contents of files on disk in virtual memory. You can
use memory-mapped files to edit very large files and to create shared memory for interprocess
communication.
File path formats on Windows systems
15 minutes to read • Edit Online
Members of many of the types in the System.IO namespace include a path parameter that lets you specify an
absolute or relative path to a file system resource. This path is then passed to Windows file system APIs. This topic
discusses the formats for file paths that you can use on Windows systems.
PATH DESCRIPTION
\Program Files\Custom Utilities\StringFinder.exe An absolute path from the root of the current drive.
IMPORTANT
Note the difference between the last two paths. Both specify the optional volume specifier (C: in both cases), but the first
begins with the root of the specified volume, whereas the second does not. As result, the first is an absolute path from the
root directory of drive C:, whereas the second is a relative path from the current directory of drive C:. Use of the second form
when the first is intended is a common source of bugs that involve Windows file paths.
You can determine whether a file path is fully qualified (that is, it the path is independent of the current directory
and does not change when the current directory changes) by calling the IsPathFullyQualified method. Note that
such a path can include relative directory segments ( . and .. ) and still be fully qualified if the resolved path
always points to the same location.
The following example illustrates the difference between absolute and relative paths. It assumes that the directory
D:\FY2018\ exists, and that you haven't set any current directory for D:\ from the command prompt before
running the example.
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
Directory.SetCurrentDirectory(@"C:\");
string path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");
// This will be "D:\Docs\FY2018" as it happens to match the drive of the current directory
Console.WriteLine($"'D:FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
// This will be either "D:\FY2018" or "D:\FY2018\FY2018" in the subprocess. In the sub process,
// the command prompt set the current directory before launch of our application, which
// sets a hidden environment variable that is considered.
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");
if (args.Length < 1)
{
Console.WriteLine(@"Launching again, after setting current directory to D:\FY2018");
Uri currentExe = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute);
string commandLine = $"/C cd D:\\FY2018 & \"{currentExe.LocalPath}\" stop";
ProcessStartInfo psi = new ProcessStartInfo("cmd", commandLine); ;
Process.Start(psi).WaitForExit();
Imports System.Diagnostics
Imports System.IO
Imports System.Reflection
filePath = Path.GetFullPath("D:\FY2018")
Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
filePath = Path.GetFullPath("D:FY2018")
' This will be "D:\Docs\FY2018" as it happens to match the drive of the current directory
Console.WriteLine($"'D:FY2018' resolves to {filePath}")
filePath = Path.GetFullPath("D:\FY2018")
Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
' This will be either "D:\FY2018" or "D:\FY2018\FY2018" in the subprocess. In the sub process,
' the command prompt set the current directory before launch of our application, which
' sets a hidden environment variable that is considered.
filePath = Path.GetFullPath("D:FY2018")
Console.WriteLine($"'D:FY2018' resolves to {filePath}")
UNC paths
Universal naming convention (UNC ) paths, which are used to access network resources, have the following format:
A server or host name, which is prefaced by \\. The server name can be a NetBIOS machine name or an
IP/FQDN address (IPv4 as well as v6 are supported).
A share name, which is separated from the host name by \. Together, the server and share name make up the
volume.
A directory name. The directory separator character separates subdirectories within the nested directory
hierarchy.
An optional filename. The directory separator character separates the file path and the filename.
The following are some examples of UNC paths:
PATH DESCRIPTION
UNC paths must always be fully qualified. They can include relative directory segments ( . and .. ), but these
must be part of a fully qualified path. You can use relative paths only by mapping a UNC path to a drive letter.
DOS device paths
The Windows operating system has a unified object model that points to all resources, including files. These object
paths are accessible from the console window and are exposed to the Win32 layer through a special folder of
symbolic links that legacy DOS and UNC paths are mapped to. This special folder is accessed via the DOS device
path syntax, which is one of:
\\.\C:\Test\Foo.txt \\?\C:\Test\Foo.txt
In addition to identifying a drive by its drive letter, you can identify a volume by using its volume GUID. This takes
the form:
\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt
\\?\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt
NOTE
DOS device path syntax is supported on .NET implementations running on Windows starting with .NET Core 1.1 and .NET
Framework 4.6.2.
NOTE
The \\?\ is supported in all versions of .NET Core and in the .NET Framework starting with version 4.6.2.
A symbolic link to the "real" device object (C: in the case of a drive name, or Volume{b75e2c83-0000-0000-
0000-602f00000000} in the case of a volume GUID ).
The first segment of the DOS device path after the device path specifier identifies the volume or drive. (For
example, \\?\C:\ and \\.\BootPartition\ .)
There is a specific link for UNCs that is called, not surprisingly, UNC . For example:
\\.\UNC\Server\Share\Test\Foo.txt \\?\UNC\Server\Share\Test\Foo.txt
For device UNCs, the server/share portion forms the volume. For example, in
\\?\server1\e:\utilities\\filecomparer\ , the server/share portion is server1\utilities. This is significant
when calling a method such as Path.GetFullPath(String, String) with relative directory segments; it is never
possible to navigate past the volume.
DOS device paths are fully qualified by definition. Relative directory segments ( . and .. ) are not allowed.
Current directories never enter into their usage.
class Program
{
static void Main()
{
string[] filenames = {
@"c:\temp\test-file.txt",
@"\\127.0.0.1\c$\temp\test-file.txt",
@"\\LOCALHOST\c$\temp\test-file.txt",
@"\\.\c:\temp\test-file.txt",
@"\\?\c:\temp\test-file.txt",
@"\\.\UNC\LOCALHOST\c$\temp\test-file.txt",
@"\\127.0.0.1\c$\temp\test-file.txt" };
Imports System.IO
Module Program
Sub Main()
Dim filenames() As String = {
"c:\temp\test-file.txt",
"\\127.0.0.1\c$\temp\test-file.txt",
"\\LOCALHOST\c$\temp\test-file.txt",
"\\.\c:\temp\test-file.txt",
"\\?\c:\temp\test-file.txt",
"\\.\UNC\LOCALHOST\c$\temp\test-file.txt",
"\\127.0.0.1\c$\temp\test-file.txt" }
Path normalization
Almost all paths passed to Windows APIs are normalized. During normalization, Windows performs the following
steps:
Identifies the path.
Applies the current directory to partially qualified (relative) paths.
Canonicalizes component and directory separators.
Evaluates relative directory components ( . for the current directory and .. for the parent directory).
Trims certain characters.
This normalization happens implicitly, but you can do it explicitly by calling the Path.GetFullPath method, which
wraps a call to the GetFullPathName() function. You can also call the Windows GetFullPathName() function
directly using P/Invoke.
Identifying the path
The first step in path normalization is identifying the type of path. Paths fall into one of a few categories:
They are device paths; that is, they begin with two separators and a question mark or period ( \\? or \\. ).
They are UNC paths; that is, they begin with two separators without a question mark or period.
They are fully qualified DOS paths; that is, they begin with a drive letter, a volume separator, and a component
separator ( C:\ ).
They designate a legacy device ( CON , LPT1 ).
They are relative to the root of the current drive; that is, they begin with a single component separator ( \ ).
They are relative to the current directory of a specified drive; that is, they begin with a drive letter, a volume
separator, and no component separator ( C: ).
They are relative to the current directory; that is, they begin with anything else ( temp\testfile.txt ).
The type of the path determines whether or not a current directory is applied in some way. It also determines what
the "root" of the path is.
Handling legacy devices
If the path is a legacy DOS device such as CON , COM1 , or LPT1 , it is converted into a device path by prepending
\\.\ and returned.
A path that begins with a legacy device name is always interpreted as a legacy device by the
Path.GetFullPath(String) method. For example, the DOS device path for CON.TXT is \\.\CON , and the DOS device
path for COM1.TXT\file1.txt is \\.\COM1 .
Applying the current directory
If a path isn't fully qualified, Windows applies the current directory to it. UNCs and device paths do not have the
current directory applied. Neither does a full drive with separator C:\.
If the path starts with a single component separator, the drive from the current directory is applied. For example, if
the file path is \utilities and the current directory is C:\temp\ , normalization produces C:\utilities .
If the path starts with a drive letter, volume separator, and no component separator, the last current directory set
from the command shell for the specified drive is applied. If the last current directory was not set, the drive alone is
applied. For example, if the file path is D:sources , the current directory is C:\Documents\ , and the last current
directory on drive D: was D:\sources\ , the result is D:\sources\sources . These "drive relative" paths are a common
source of program and script logic errors. Assuming that a path beginning with a letter and a colon isn't relative is
obviously not correct.
If the path starts with something other than a separator, the current drive and current directory are applied. For
example, if the path is filecompare and the current directory is C:\utilities\ , the result is
C:\utilities\filecompare\ .
IMPORTANT
Relative paths are dangerous in multithreaded applications (that is, most applications) because the current directory is a per-
process setting. Any thread can change the current directory at any time. Starting with .NET Core 2.1, you can call the
Path.GetFullPath(String, String) method to get an absolute path from a relative path and the base path (the current
directory) that you want to resolve it against.
Canonicalizing separators
All forward slashes ( / ) are converted into the standard Windows separator, the back slash ( \ ). If they are
present, a series of slashes that follow the first two slashes are collapsed into a single slash.
Evaluating relative components
As the path is processed, any components or segments that are composed of a single or a double period ( . or
.. ) are evaluated:
For a single period, the current segment is removed, since it refers to the current directory.
For a double period, the current segment and the parent segment are removed, since the double period
refers to the parent directory.
Parent directories are only removed if they aren't past the root of the path. The root of the path depends on
the type of path. It is the drive ( C:\ ) for DOS paths, the server/share for UNCs ( \\Server\Share ), and the
device path prefix for device paths ( \\?\ or \\.\ ).
Trimming characters
Along with the runs of separators and relative segments removed earlier, some additional characters are removed
during normalization:
If a segment ends in a single period, that period is removed. (A segment of a single or double period is
normalized in the previous step. A segment of three or more periods is not normalized and is actually a
valid file/directory name.)
If the path doesn't end in a separator, all trailing periods and spaces (U+0020) are removed. If the last
segment is simply a single or double period, it falls under the relative components rule above.
This rule means that you can create a directory name with a trailing space by adding a trailing separator
after the space.
IMPORTANT
You should never create a directory or filename with a trailing space. Trailing spaces can make it difficult or
impossible to access a directory, and applications commonly fail when attempting to handle directories or files whose
names include trailing spaces.
Skipping normalization
Normally, any path passed to a Windows API is (effectively) passed to the GetFullPathName function and
normalized. There is one important exception: a device path that begins with a question mark instead of a period.
Unless the path starts exactly with \\?\ (note the use of the canonical backslash), it is normalized.
Why would you want to skip normalization? There are three major reasons:
1. To get access to paths that are normally unavailable but are legal. A file or directory called hidden. , for
example, is impossible to access in any other way.
2. To improve performance by skipping normalization if you've already normalized.
3. On the .NET Framework only, to skip the MAX_PATH check for path length to allow for paths that are greater
than 259 characters. Most APIs allow this, with some exceptions.
NOTE
.NET Core handles long paths implicitly and does not perform a MAX_PATH check. The MAX_PATH check applies only to the
.NET Framework.
Skipping normalization and max path checks is the only difference between the two device path syntaxes; they are
otherwise identical. Be careful with skipping normalization, since you can easily create paths that are difficult for
"normal" applications to deal with.
Paths that start with \\?\ are still normalized if you explicitly pass them to the GetFullPathName function.
You can pass paths of more than MAX_PATH characters to GetFullPathName without \\?\ . It supports arbitrary
length paths up to the maximum string size that Windows can handle.
Directory.Create("TeStDiReCtOrY");
Directory.Create("TeStDiReCtOrY")
creates a directory named TeStDiReCtOrY. If you rename a directory or file to change its case, the directory or file
name reflects the case of the string used when you rename it. For example, the following code renames a file
named test.txt to Test.txt:
using System.IO;
class Example
{
static void Main()
{
var fi = new FileInfo(@".\test.txt");
fi.MoveTo(@".\Test.txt");
}
}
Imports System.IO
Module Example
Public Sub Main()
Dim fi As New FileInfo(".\test.txt")
fi.MoveTo(".\Test.txt")
End Sub
End Module
However, directory and file name comparisons are case-insensitive. If you search for a file named "test.txt", .NET
file system APIs ignore case in the comparison. Test.txt, TEST.TXT, test.TXT, and any other combination of upper-
and lowercase letters will match "test.txt".
Common I/O Tasks
2 minutes to read • Edit Online
The System.IO namespace provides several classes that allow for various actions, such as reading and writing, to
be performed on files, directories, and streams. For more information, see File and Stream I/O.
FileInfo.CreateText method
File.Create method
FileInfo.Create method
Read from a text file How to: Read Text from a File
Append text to a file How to: Open and Append to a Log File
File.AppendText method
FileInfo.AppendText method
FileInfo.MoveTo method
FileInfo.Delete method
FileInfo.CopyTo method
Read from a binary file How to: Read and Write to a Newly Created Data File
TO DO THIS... SEE THE EXAMPLE IN THIS TOPIC...
Write to a binary file How to: Read and Write to a Newly Created Data File
Retrieve the file name and extension from a path Path.GetFileName method
Access a file in a special folder such as My Documents How to: Write Text to a File
FileInfo.Directory property
DirectoryInfo.MoveTo method
DirectoryInfo.Delete method
See the files and subdirectories in a directory How to: Enumerate Directories and Files
See also
File and Stream I/O
Composing Streams
Asynchronous File I/O
How to: Copy directories
2 minutes to read • Edit Online
This topic demonstrates how to use I/O classes to synchronously copy the contents of a directory to another
location.
For an example of asynchronous file copy, see Asynchronous file I/O.
This example copies subdirectories by setting the copySubDirs of the DirectoryCopy method to true . The
DirectoryCopy method recursively copies subdirectories by calling itself on each subdirectory until there are no
more to copy.
Example
using System;
using System.IO;
class DirectoryCopyExample
{
static void Main()
{
// Copy from the current directory, include subdirectories.
DirectoryCopy(".", @".\temp", true);
}
if (!dir.Exists)
{
throw new DirectoryNotFoundException(
"Source directory does not exist or could not be found: "
+ sourceDirName);
}
// Get the files in the directory and copy them to the new location.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, false);
}
Class DirectoryCopyExample
' If copying subdirectories, copy them and their contents to new location.
If copySubDirs Then
For Each subdir In dirs
Dim temppath As String = Path.Combine(destDirName, subdir.Name)
DirectoryCopy(subdir.FullName, temppath, copySubDirs)
Next subdir
End If
End Sub
End Class
See also
FileInfo
DirectoryInfo
FileStream
File and stream I/O
Common I/O tasks
Asynchronous file I/O
How to: Enumerate directories and files
4 minutes to read • Edit Online
Enumerable collections provide better performance than arrays when you work with large collections of
directories and files. To enumerate directories and files, use methods that return an enumerable collection of
directory or file names, or their DirectoryInfo, FileInfo, or FileSystemInfo objects.
If you want to search and return only the names of directories or files, use the enumeration methods of the
Directory class. If you want to search and return other properties of directories or files, use the DirectoryInfo and
FileSystemInfo classes.
You can use enumerable collections from these methods as the IEnumerable<T> parameter for constructors of
collection classes like List<T>.
The following table summarizes the methods that return enumerable collections of files and directories:
NOTE
Although you can immediately enumerate all the files in the subdirectories of a parent directory by using the AllDirectories
option of the optional SearchOption enumeration, UnauthorizedAccessException errors may make the enumeration
incomplete. You can catch these exceptions by first enumerating directories and then enumerating files.
class Program
{
private static void Main(string[] args)
{
try
{
// Set a variable to the My Documents path.
string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
Imports System.Collections.Generic
Imports System.IO
Module Module1
Sub Main()
Try
Dim dirPath As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
End Sub
End Module
The following example uses the Directory.EnumerateFiles(String, String, SearchOption) method to recursively
enumerate all file names in a directory and subdirectories that match a certain pattern. It then reads each line of
each file and displays the lines that contain a specified string, with their filenames and paths.
using System;
using System.IO;
using System.Linq;
class Program
{
static void Main(string[] args)
{
try
{
// Set a variable to the My Documents path.
string docPath =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
Imports System.IO
Imports System.Xml.Linq
Module Module1
Sub Main()
Try
Dim docPath As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
Dim files = From chkFile In Directory.EnumerateFiles(docPath, "*.txt",
SearchOption.AllDirectories)
From line In File.ReadLines(chkFile)
Where line.Contains("Microsoft")
Select New With {.curFile = chkFile, .curLine = line}
using System;
using System.IO;
namespace EnumDir
{
class Program
{
static void Main(string[] args)
{
// Set a variable to the Documents path.
string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
}
}
}
// </Snippet1>
Imports System.IO
Module Module1
Sub Main()
End Sub
End Module
The following example uses the DirectoryInfo.EnumerateFiles method to list all files whose Length exceeds 10MB.
This example first enumerates the top-level directories, to catch possible unauthorized access exceptions, and then
enumerates the files.
using System;
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
// Set a variable to the My Documents path.
string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
try
{
foreach (var fi in diTop.EnumerateFiles())
{
try
{
// Display each file over 10 MB;
if (fi.Length > 10000000)
{
Console.WriteLine($"{fi.FullName}\t\t{fi.Length.ToString("NO")}");
}
}
catch (UnauthorizedAccessException unAuthTop)
{
Console.WriteLine($"{unAuthTop.Message}");
}
}
Imports System.IO
Class Program
Public Shared Sub Main(ByVal args As String())
Dim dirPath As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
Dim diTop As New DirectoryInfo(dirPath)
Try
For Each fi In diTop.EnumerateFiles()
Try
' Display each file over 10 MB;
If fi.Length > 10000000 Then
Console.WriteLine("{0}" & vbTab & vbTab & "{1}", fi.FullName,
fi.Length.ToString("N0"))
End If
Catch unAuthTop As UnauthorizedAccessException
Console.WriteLine($"{unAuthTop.Message}")
End Try
Next
See also
File and stream I/O
How to: Read and write to a newly created data file
2 minutes to read • Edit Online
The System.IO.BinaryWriter and System.IO.BinaryReader classes are used for writing and reading data other than
character strings. The following example shows how to create an empty file stream, write data to it, and read data
from it.
The example creates a data file called Test.data in the current directory, creates the associated BinaryWriter and
BinaryReader objects, and uses the BinaryWriter object to write the integers 0 through 10 to Test.data, which
leaves the file pointer at the end of the file. The BinaryReader object then sets the file pointer back to the origin
and reads out the specified content.
NOTE
If Test.data already exists in the current directory, an IOException exception is thrown. Use the file mode option
FileMode.Create rather than FileMode.CreateNew to always create a new file without throwing an exception.
Example
using System;
using System.IO;
class MyStream
{
private const string FILE_NAME = "Test.data";
// The example creates a file named "Test.data" and writes the integers 0 through 10 to it in binary format.
// It then writes the contents of Test.data to the console with each integer on a separate line.
Imports System.IO
Class MyStream
Private Const FILE_NAME As String = "Test.data"
' The example creates a file named "Test.data" and writes the integers 0 through 10 to it in binary format.
' It then writes the contents of Test.data to the console with each integer on a separate line.
See also
BinaryReader
BinaryWriter
FileStream
FileStream.Seek
SeekOrigin
How to: Enumerate directories and files
How to: Open and append to a log file
How to: Read text from a file
How to: Write text to a file
How to: Read characters from a string
How to: Write characters to a string
File and stream I/O
How to: Open and append to a log file
2 minutes to read • Edit Online
StreamWriter and StreamReader write characters to and read characters from streams. The following code
example opens the log.txt file for input, or creates it if it doesn't exist, and appends log information to the end of
the file. The example then writes the contents of the file to standard output for display.
As an alternative to this example, you could store the information as a single string or string array, and use the
File.WriteAllText or File.WriteAllLines method to achieve the same functionality.
NOTE
Visual Basic users may choose to use the methods and properties provided by the Log class or FileSystem class for creating
or writing to log files.
Example
using System;
using System.IO;
class DirAppend
{
public static void Main()
{
using (StreamWriter w = File.AppendText("log.txt"))
{
Log("Test1", w);
Log("Test2", w);
}
// Log Entry : <current long time string> <current long date string>
// :
// :Test1
// -------------------------------
// Log Entry : <current long time string> <current long date string>
// :
// :Test2
// -------------------------------
Class DirAppend
Public Shared Sub Main()
Using w As StreamWriter = File.AppendText("log.txt")
Log("Test1", w)
Log("Test2", w)
End Using
' The example creates a file named "log.txt" and writes the following lines to it,
' or appends them to the existing "log.txt" file:
' Log Entry : <current long time string> <current long date string>
' :
' :Test1
' -------------------------------
' Log Entry : <current long time string> <current long date string>
' :
' :Test2
' -------------------------------
See also
StreamWriter
StreamReader
File.AppendText
File.OpenText
StreamReader.ReadLine
How to: Enumerate directories and files
How to: Read and write to a newly created data file
How to: Read text from a file
How to: Write text to a file
How to: Read characters from a string
How to: Write characters to a string
File and stream I/O
How to: Write text to a file
5 minutes to read • Edit Online
This topic shows different ways to write text to a file for a .NET app.
The following classes and methods are typically used to write text to a file:
StreamWriter contains methods to write to a file synchronously (Write and WriteLine) or asynchronously
(WriteAsync and WriteLineAsync).
File provides static methods to write text to a file, such as WriteAllLines and WriteAllText, or to append text
to a file, such as AppendAllLines, AppendAllText, and AppendText.
Path is for strings that have file or directory path information. It contains the Combine method and, in
.NET Core 2.1 and later, the Join and TryJoin methods, which allow concatenation of strings to build a file
or directory path.
NOTE
The following examples show only the minimum amount of code needed. A real-world app usually provides more robust
error checking and exception handling.
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
Class WriteText
End Sub
End Class
' The example creates a file named "WriteLines.txt" with the following contents:
' First line
' Second line
' Third line
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
Class AppendText
End Sub
End Class
' The example adds the following line to the contents of "WriteLines.txt":
' Fourth Line
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
// Set a variable to the Documents path.
string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
' The example creates a file named "WriteTextAsync.txt" with the following contents:
' This is a sentence.
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
// Create a string with a line of text.
string text = "First line" + Environment.NewLine;
Class WriteFile
End Sub
End Class
' The example creates a file named "WriteFile.txt" with the following contents:
' First line
' And then appends the following contents:
' New line 1
' New line 2
See also
StreamWriter
Path
File.CreateText
How to: Enumerate directories and files
How to: Read and write to a newly-created data file
How to: Open and append to a log file
How to: Read text from a file
File and stream I/O
How to: Read text from a file
2 minutes to read • Edit Online
The following examples show how to read text synchronously and asynchronously from a text file using .NET for
desktop apps. In both examples, when you create the instance of the StreamReader class, you provide the relative
or absolute path to the file.
NOTE
These code examples do not apply to developing for Universal Windows (UWP) apps, because the Windows Runtime
provides different stream types for reading and writing to files. For an example that shows how to read text from a file in a
UWP app, see Quickstart: Reading and writing files. For examples that show how to convert between .NET Framework
streams and Windows Runtime streams, see How to: Convert between .NET Framework streams and Windows Runtime
streams.
IMPORTANT
The example assumes that a file named TestFile.txt already exists in the same folder as the app.
using System;
using System.IO;
class Test
{
public static void Main()
{
try
{ // Open the text file using a stream reader.
using (StreamReader sr = new StreamReader("TestFile.txt"))
{
// Read the stream to a string, and write the string to the console.
String line = sr.ReadToEnd();
Console.WriteLine(line);
}
}
catch (IOException e)
{
Console.WriteLine("The file could not be read:");
Console.WriteLine(e.Message);
}
}
}
Imports System.IO
Class Test
Public Shared Sub Main()
Try
' Open the file using a stream reader.
Using sr As New StreamReader("TestFile.txt")
' Read the stream to a string and write the string to the console.
Dim line = sr.ReadToEnd()
Console.WriteLine(line)
End Using
Catch e As IOException
Console.WriteLine("The file could not be read:")
Console.WriteLine(e.Message)
End Try
End Sub
End Class
IMPORTANT
The example assumes that a file named TestFile.txt already exists in the same folder as the app.
using System;
using System.IO;
using System.Windows;
namespace TextFiles
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
''' <summary>
''' Interaction logic for MainWindow.xaml
''' </summary>
See also
StreamReader
File.OpenText
StreamReader.ReadLine
Asynchronous file I/O
How to: Create a directory listing
Quickstart: Reading and writing files
How to: Convert between .NET Framework streams and Windows Runtime streams
How to: Read and write to a newly created data file
How to: Open and append to a log file
How to: Write text to a file
How to: Read characters from a string
How to: Write characters to a string
File and stream I/O
How to: Read characters from a string
2 minutes to read • Edit Online
The following code examples show how to read characters synchronously or asynchronously from a string.
using System;
using System.IO;
// Read the rest of the string starting at the current string position.
// Put in the array starting at the 6th array member.
sr.Read(b, 5, str.Length - 13);
Console.WriteLine(b);
}
}
}
// The example has the following output:
//
// Some number o
// Some f characters
Imports System.IO
' Read the rest of the string starting at the current string position.
' Put in the array starting at the 6th array member.
sr.Read(b, 5, str.Length - 13)
Console.WriteLine(b)
End Using
End Sub
End Class
' The example has the following output:
'
' Some number o
' Some f characters
namespace StringReaderWriter
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
''' <summary>
''' Interaction logic for MainWindow.xaml
''' </summary>
See also
StringReader
StringReader.Read
Asynchronous file I/O
How to: Create a directory listing
How to: Read and write to a newly created data file
How to: Open and append to a log file
How to: Read text from a file
How to: Write text to a file
How to: Write characters to a string
File and stream I/O
How to: Write characters to a string
2 minutes to read • Edit Online
The following code examples write characters synchronously or asynchronously from a character array into a
string.
using System;
using System.IO;
using System.Text;
Imports System.IO
Imports System.Text
using System;
using System.Text;
using System.Windows;
using System.IO;
namespace StringReaderWriter
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
''' <summary>
''' Interaction logic for MainWindow.xaml
''' </summary>
See also
StringWriter
StringWriter.Write
StringBuilder
File and stream I/O
Asynchronous file I/O
How to: Enumerate directories and files
How to: Read and write to a newly created data file
How to: Open and append to a log file
How to: Read text from a file
How to: Write text to a file
How to: Read characters from a string
How to: Add or remove Access Control List entries
(.NET Framework only)
2 minutes to read • Edit Online
To add or remove Access Control List (ACL ) entries to or from a file or directory, get the FileSecurity or
DirectorySecurity object from the file or directory. Modify the object, and then apply it back to the file or directory.
Example
You must use a valid user or group account to run this example. The example uses a File object. Use the same
procedure for the FileInfo, Directory, and DirectoryInfo classes.
using System;
using System.IO;
using System.Security.AccessControl;
namespace FileSystemExample
{
class FileExample
{
public static void Main()
{
try
{
string fileName = "test.xml";
Console.WriteLine("Done.");
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
// Adds an ACL entry on the specified file for the specified account.
public static void AddFileSecurity(string fileName, string account,
FileSystemRights rights, AccessControlType controlType)
{
// Removes an ACL entry on the specified file for the specified account.
public static void RemoveFileSecurity(string fileName, string account,
FileSystemRights rights, AccessControlType controlType)
{
}
}
}
Imports System.IO
Imports System.Security.AccessControl
Module FileExample
Sub Main()
Try
Dim fileName As String = "test.xml"
Console.WriteLine("Done.")
Catch e As Exception
Console.WriteLine(e)
End Try
End Sub
' Adds an ACL entry on the specified file for the specified account.
Sub AddFileSecurity(ByVal fileName As String, ByVal account As String, _
ByVal rights As FileSystemRights, ByVal controlType As AccessControlType)
fSecurity.AddAccessRule(accessRule)
End Sub
' Removes an ACL entry on the specified file for the specified account.
Sub RemoveFileSecurity(ByVal fileName As String, ByVal account As String, _
ByVal rights As FileSystemRights, ByVal controlType As AccessControlType)
End Sub
End Module
How to: Compress and extract files
5 minutes to read • Edit Online
The System.IO.Compression namespace contains the following types for compressing and decompressing files
and streams. You can also use these types to read and modify the contents of a compressed file.
ZipFile
ZipArchive
ZipArchiveEntry
DeflateStream
GZipStream
The following examples show some of the operations you can perform with compressed files.
using System;
using System.IO.Compression;
class Program
{
static void Main(string[] args)
{
string startPath = @".\start";
string zipPath = @".\result.zip";
string extractPath = @".\extract";
ZipFile.CreateFromDirectory(startPath, zipPath);
ZipFile.ExtractToDirectory(zipPath, extractPath);
}
}
Imports System.IO.Compression
Module Module1
Sub Main()
Dim startPath As String = ".\start"
Dim zipPath As String = ".\result.zip"
Dim extractPath As String = ".\extract"
ZipFile.CreateFromDirectory(startPath, zipPath)
ZipFile.ExtractToDirectory(zipPath, extractPath)
End Sub
End Module
If you get the error "The type 'ZipArchive' is defined in an assembly that is not referenced," add a reference to the
System.IO.Compression assembly to your project.
IMPORTANT
When unzipping files, you must look for malicious file paths, which can escape out of the directory you unzip into. This is
known as a path traversal attack. The following example demonstrates how to check for malicious file paths and provides a
safe way to unzip.
using System;
using System.IO;
using System.IO.Compression;
class Program
{
static void Main(string[] args)
{
string zipPath = @".\result.zip";
// Ordinal match is safest, case-sensitive volumes can be mounted within volumes that
// are case-insensitive.
if (destinationPath.StartsWith(extractPath, StringComparison.Ordinal))
entry.ExtractToFile(destinationPath);
}
}
}
}
}
Imports System.IO
Imports System.IO.Compression
Module Module1
Sub Main()
Dim zipPath As String = ".\result.zip"
' Gets the full path to ensure that relative segments are removed.
Dim destinationPath As String = Path.GetFullPath(Path.Combine(extractPath,
entry.FullName))
' Ordinal match is safest, case-sensitive volumes can be mounted within volumes that
' are case-insensitive.
If destinationPath.StartsWith(extractPath, StringComparison.Ordinal) Then
entry.ExtractToFile(destinationPath)
End If
End If
Next
End Using
End Sub
End Module
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
using (FileStream zipToOpen = new FileStream(@"c:\users\exampleuser\release.zip", FileMode.Open))
{
using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Update))
{
ZipArchiveEntry readmeEntry = archive.CreateEntry("Readme.txt");
using (StreamWriter writer = new StreamWriter(readmeEntry.Open()))
{
writer.WriteLine("Information about this package.");
writer.WriteLine("========================");
}
}
}
}
}
}
Imports System.IO
Imports System.IO.Compression
Module Module1
Sub Main()
Using zipToOpen As FileStream = New FileStream("c:\users\exampleuser\release.zip", FileMode.Open)
Using archive As ZipArchive = New ZipArchive(zipToOpen, ZipArchiveMode.Update)
Dim readmeEntry As ZipArchiveEntry = archive.CreateEntry("Readme.txt")
Using writer As StreamWriter = New StreamWriter(readmeEntry.Open())
writer.WriteLine("Information about this package.")
writer.WriteLine("========================")
End Using
End Using
End Using
End Sub
End Module
}
}
FileInfo info = new FileInfo(directoryPath + Path.DirectorySeparatorChar +
fileToCompress.Name + ".gz");
Console.WriteLine($"Compressed {fileToCompress.Name} from
{fileToCompress.Length.ToString()} to {info.Length.ToString()} bytes.");
}
}
}
}
Module Module1
originalFileStream.CopyTo(compressionStream)
End Using
End Using
Dim info As New FileInfo(directoryPath & Path.DirectorySeparatorChar & fileToCompress.Name
& ".gz")
Console.WriteLine($"Compressed {fileToCompress.Name} from
{fileToCompress.Length.ToString()} to {info.Length.ToString()} bytes.")
End If
End Using
Next
End Sub
See also
ZipArchive
ZipFile
ZipArchiveEntry
DeflateStream
GZipStream
File and stream I/O
Compose streams
2 minutes to read • Edit Online
A backing store is a storage medium, such as a disk or memory. Each different backing store implements its own
stream as an implementation of the Stream class.
Each stream type reads and writes bytes from and to its given backing store. Streams that connect to backing
stores are called base streams. Base streams have constructors with the parameters necessary to connect the
stream to the backing store. For example, FileStream has constructors that specify a path parameter, which
specifies how the file will be shared by processes.
The design of the System.IO classes provides simplified stream composition. You can attach base streams to one or
more pass-through streams that provide the functionality you want. You can attach a reader or writer to the end of
the chain, so the preferred types can be read or written easily.
The following code examples create a FileStream around the existing MyFile.txt in order to buffer MyFile.txt. Note
that FileStreams are buffered by default.
IMPORTANT
The examples assume that a file named MyFile.txt already exists in the same folder as the app.
Imports System.IO
Imports System.IO
See also
StreamReader
StreamReader.ReadLine
StreamReader.Peek
FileStream
BinaryReader
BinaryReader.ReadByte
BinaryReader.PeekChar
How to: Convert between .NET Framework and
Windows Runtime streams (Windows only)
6 minutes to read • Edit Online
The .NET Framework for UWP apps is a subset of the full .NET Framework. Because of security and other
requirements for UWP apps, you can't use the full set of .NET Framework APIs to open and read files. For more
information, see .NET for UWP apps overview. However, you may want to use .NET Framework APIs for other
stream manipulation operations. To manipulate these streams, you can convert between a .NET Framework
stream type such as MemoryStream or FileStream, and a Windows Runtime stream such as IInputStream,
IOutputStream, or IRandomAccessStream.
The System.IO.WindowsRuntimeStreamExtensions class contains methods that make these conversions easy.
However, underlying differences between .NET Framework and Windows Runtime streams affect the results of
using these methods, as described in the following sections:
using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Storage;
using System.Net.Http;
using Windows.Storage.Pickers;
// Set properties on the file picker such as start location and the type
// of files to display.
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
picker.ViewMode = PickerViewMode.List;
picker.FileTypeFilter.Add(".txt");
if (result != null)
{
try
{
// Retrieve the stream. This method returns a IRandomAccessStreamWithContentType.
var stream = await result.OpenReadAsync();
' Set properties on the file picker such as start location and the type of files to display.
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary
picker.ViewMode = PickerViewMode.List
picker.FileTypeFilter.Add(".txt")
IMPORTANT
Make sure that the .NET Framework stream you are using supports seeking, or copy it to a stream that does. You can use
the Stream.CanSeek property to determine this.
To run this example, create a UWP XAML app that targets the .NET Framework 4.5.1 and contains a text block
named TextBlock2 and a button named Button2 . Associate the button click event with the button2_Click
method shown in the example.
using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Storage;
using System.Net.Http;
using Windows.Storage.Pickers;
' Convert the stream to the memory stream, because a memory stream supports seeking.
Await stream.CopyToAsync(memStream)
' Set the bitmap source to the stream, which is converted to a IRandomAccessStream.
bitmap.SetSource(memStream.AsRandomAccessStream())
See also
Quickstart: Read and write a file (Windows)
.NET for Windows Store apps overview
.NET for Windows Store apps APIs
Asynchronous File I/O
4 minutes to read • Edit Online
Asynchronous operations enable you to perform resource-intensive I/O operations without blocking the main
thread. This performance consideration is particularly important in a Windows 8.x Store app or desktop app
where a time-consuming stream operation can block the UI thread and make your app appear as if it is not
working.
Starting with the .NET Framework 4.5, the I/O types include async methods to simplify asynchronous
operations. An async method contains Async in its name, such as ReadAsync, WriteAsync, CopyToAsync,
FlushAsync, ReadLineAsync, and ReadToEndAsync. These async methods are implemented on stream classes,
such as Stream, FileStream, and MemoryStream, and on classes that are used for reading from or writing to
streams, such TextReader and TextWriter.
In the .NET Framework 4 and earlier versions, you have to use methods such as BeginRead and EndRead to
implement asynchronous I/O operations. These methods are still available in the .NET Framework 4.5 to
support legacy code; however, the async methods help you implement asynchronous I/O operations more easily.
C# and Visual Basic each have two keywords for asynchronous programming:
Async (Visual Basic) or async (C#) modifier, which is used to mark a method that contains an
asynchronous operation.
Await (Visual Basic) or await (C#) operator, which is applied to the result of an async method.
To implement asynchronous I/O operations, use these keywords in conjunction with the async methods, as
shown in the following examples. For more information, see Asynchronous programming with async and await
(C#) or Asynchronous Programming with Async and Await (Visual Basic).
The following example demonstrates how to use two FileStream objects to copy files asynchronously from one
directory to another. Notice that the Click event handler for the Button control is marked with the async
modifier because it calls an asynchronous method.
using System;
using System.Threading.Tasks;
using System.Windows;
using System.IO;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
Imports System.IO
Class MainWindow
End Using
Next
End Sub
End Class
The next example is similar to the previous one but uses StreamReader and StreamWriter objects to read and
write the contents of a text file asynchronously.
private async void Button_Click(object sender, RoutedEventArgs e)
{
string UserDirectory = @"c:\Users\exampleuser\";
End Function
The next example shows the code-behind file and the XAML file that are used to open a file as a Stream in a
Windows 8.x Store app, and read its contents by using an instance of the StreamReader class. It uses
asynchronous methods to open the file as a stream and to read its contents.
using System;
using System.IO;
using System.Text;
using Windows.Storage.Pickers;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace ExampleApplication
{
public sealed partial class BlankPage : Page
{
public BlankPage()
{
this.InitializeComponent();
}
openPicker.FileTypeFilter.Add(".txt")
Dim selectedFile As StorageFile = Await openPicker.PickSingleFileAsync()
<Page
x:Class="ExampleApplication.BlankPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ExampleApplication"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
See also
Stream
File and Stream I/O
Asynchronous programming with async and await (C#)
Asynchronous Programming with Async and Await (Visual Basic)
Handling I/O errors in .NET
5 minutes to read • Edit Online
In addition to the exceptions that can be thrown in any method call (such as an OutOfMemoryException when a
system is stressed or an NullReferenceException due to programmer error), .NET file system methods can throw
the following exceptions:
System.IO.IOException, the base class of all System.IO exception types. It is thrown for errors whose return
codes from the operating system don't directly map to any other exception type.
System.IO.FileNotFoundException.
System.IO.DirectoryNotFoundException.
DriveNotFoundException.
System.IO.PathTooLongException.
System.OperationCanceledException.
System.UnauthorizedAccessException.
System.ArgumentException, which is thrown for invalid path characters on .NET Framework and on .NET Core
2.0 and previous versions.
System.NotSupportedException, which is thrown for invalid colons in .NET Framework.
System.Security.SecurityException, which is thrown for applications running in limited trust that lack the
necessary permissions on .NET Framework only. (Full trust is the default on .NET Framework.)
NotSupportedException No Yes
Handling IOException
As the base class for exceptions in the System.IO namespace, IOException is also thrown for any error code that
does not map to a predefined exception type. This means that it can be thrown by any I/O operation.
IMPORTANT
Because IOException is the base class of the other exception types in the System.IO namespace, you should handle in a
catch block after you've handled the other I/O-related exceptions.
In addition, starting with .NET Core 2.1, validation checks for path correctness (for example, to ensure that invalid
characters are not present in a path) have been removed, and the runtime throws an exception mapped from an
operating system error code rather than from its own validation code. The most likely exception to be thrown in
this case is an IOException, although any other exception type could also be thrown.
Note that, in your exception handling code, you should always handle the IOException last. Otherwise, because it is
the base class of all other IO exceptions, the catch blocks of derived classes will not be evaluated.
In the case of an IOException, you can get additional error information from the IOException.HResult property. To
convert the HResult value to a Win32 error code, you strip out the upper 16 bits of the 32-bit value. The following
table lists error codes that may be wrapped in an IOException.
You can handle these using a When clause in a catch statement, as the following example shows.
using System;
using System.IO;
using System.Text;
class Program
{
static void Main()
{
var sw = OpenStream(@".\textfile.txt");
if (sw is null)
return;
sw.WriteLine("This is the first line.");
sw.WriteLine("This is the second line.");
sw.Close();
}
try {
var fs = new FileStream(path, FileMode.CreateNew);
return new StreamWriter(fs);
}
catch (FileNotFoundException) {
Console.WriteLine("The file or directory cannot be found.");
}
catch (DirectoryNotFoundException) {
Console.WriteLine("The file or directory cannot be found.");
}
catch (DriveNotFoundException) {
Console.WriteLine("The drive specified in 'path' is invalid.");
}
catch (PathTooLongException) {
Console.WriteLine("'path' exceeds the maxium supported path length.");
}
catch (UnauthorizedAccessException) {
Console.WriteLine("You do not have permission to create this file.");
}
catch (IOException e) when ((e.HResult & 0x0000FFFF) == 32 ) {
Console.WriteLine("There is a sharing violation.");
}
catch (IOException e) when ((e.HResult & 0x0000FFFF) == 80) {
Console.WriteLine("The file already exists.");
}
catch (IOException e) {
Console.WriteLine($"An exception occurred:\nError code: " +
$"{e.HResult & 0x0000FFFF}\nMessage: {e.Message}");
}
return null;
}
}
Imports System.IO
Module Program
Sub Main(args As String())
Dim sw = OpenStream(".\textfile.txt")
If sw Is Nothing Then Return
Try
Dim fs As New FileStream(path, FileMode.CreateNew)
Return New StreamWriter(fs)
Catch e As FileNotFoundException
Console.WriteLine("The file or directory cannot be found.")
Catch e As DirectoryNotFoundException
Console.WriteLine("The file or directory cannot be found.")
Catch e As DriveNotFoundException
Console.WriteLine("The drive specified in 'path' is invalid.")
Catch e As PathTooLongException
Console.WriteLine("'path' exceeds the maxium supported path length.")
Catch e As UnauthorizedAccessException
Console.WriteLine("You do not have permission to create this file.")
Catch e As IOException When (e.HResult And &h0000FFFF) = 32
Console.WriteLine("There is a sharing violation.")
Catch e As IOException When (e.HResult And &h0000FFFF) = 80
Console.WriteLine("The file already exists.")
Catch e As IOException
Console.WriteLine($"An exception occurred:{vbCrLf}Error code: " +
$"{e.HResult And &h0000FFFF}{vbCrLf}Message: {e.Message}")
End Try
Return Nothing
End Function
End Module
See also
Handling and throwing exceptions in .NET
Exception handling (Task Parallel Library)
Best practices for exceptions
How to use specific exceptions in a catch block
Isolated Storage
11 minutes to read • Edit Online
For desktop apps, isolated storage is a data storage mechanism that provides isolation and safety by defining
standardized ways of associating code with saved data. Standardization provides other benefits as well.
Administrators can use tools designed to manipulate isolated storage to configure file storage space, set security
policies, and delete unused data. With isolated storage, your code no longer needs unique paths to specify safe
locations in the file system, and data is protected from other applications that only have isolated storage access.
Hard-coded information that indicates where an application's storage area is located is unnecessary.
IMPORTANT
Isolated storage is not available for Windows 8.x Store apps. Instead, use the application data classes in the
Windows.Storage namespaces included in the Windows Runtime API to store local data and files. For more information,
see Application data in the Windows Dev Center.
Secure Access
Using isolated storage enables partially trusted applications to store data in a manner that is controlled by the
computer's security policy. This is especially useful for downloaded components that a user might want to run
cautiously. Security policy rarely grants this kind of code permission when you access the file system by using
standard I/O mechanisms. However, by default, code running from the local computer, a local network, or the
Internet is granted the right to use isolated storage.
Administrators can limit how much isolated storage an application or a user has available, based on an
appropriate trust level. In addition, administrators can remove a user's persisted data completely. To create or
access isolated storage, code must be granted the appropriate IsolatedStorageFilePermission permission.
To access isolated storage, code must have all necessary native platform operating system rights. The access
control lists (ACLs) that control which users have the rights to use the file system must be satisfied. .NET
Framework applications already have operating system rights to access isolated storage unless they perform
(platform-specific) impersonation. In this case, the application is responsible for ensuring that the impersonated
user identity has the proper operating system rights to access isolated storage. This access provides a convenient
way for code that is run or downloaded from the web to read and write to a storage area related to a particular
user.
To control access to isolated storage, the common language runtime uses IsolatedStorageFilePermission objects.
Each object has properties that specify the following values:
Allowed usage, which indicates the type of access that is allowed. The values are members of the
IsolatedStorageContainment enumeration. For more information about these values, see the table in the
next section.
Storage quota, as discussed in the preceding section.
The runtime demands IsolatedStorageFilePermission permission when code first attempts to open a store. It
decides whether to grant this permission, based on how much the code is trusted. If the permission is granted,
the allowed usage and storage quota values are determined by security policy and by the code's request for
IsolatedStorageFilePermission. Security policy is set by using the .NET Framework Configuration Tool
(Mscorcfg.msc). All callers in the call stack are checked to ensure that each caller has at least the appropriate
allowed usage. The runtime also checks the quota imposed on the code that opened or created the store in which
the file is to be saved. If these conditions are satisfied, permission is granted. The quota is checked again every
time a file is written to the store.
Application code is not required to request permission because the common language runtime will grant
whatever IsolatedStorageFilePermission is appropriate based on security policy. However, there are good
reasons to request specific permissions that your application needs, including IsolatedStorageFilePermission.
DomainIsolationByUser Isolation by user, domain, and This permission level leaves resources
assembly. Each assembly has a open to unauthorized overuse,
separate substore within the domain. although enforced quotas make it
Stores that use this permission are also more difficult. This is called a denial of
implicitly isolated by computer. service attack.
AssemblyIsolationByUser Isolation by user and assembly. Stores Quotas are enforced at this level to
that use this permission are also help prevent a denial of service attack.
implicitly isolated by computer. The same assembly in another domain
can access this store, opening the
possibility that information could be
leaked between applications.
AdministerIsolatedStorageByUser Isolation by user. Typically, only Access with this permission allows code
administrative or debugging tools use to view or delete any of a user's
this level of permission. isolated storage files or directories
(regardless of assembly isolation). Risks
include, but are not limited to, leaking
information and data loss.
UnrestrictedIsolatedStorage Isolation by all users, domains, and This permission creates the potential
assemblies. Typically, only for a total compromise of all isolated
administrative or debugging tools use stores for all users.
this level of permission.
Windows 2000, Windows XP, Windows Server 2003 (upgrade Roaming-enabled stores =
from Windows NT 4.0)
<SYSTEMROOT>\Profiles\<user>\Application Data
Nonroaming stores =
<SYSTEMROOT>\Profiles\<user>\Local Settings\Application
Data
Nonroaming stores =
Windows XP, Windows Server 2003 - clean installation (and Roaming-enabled stores =
upgrades from Windows 2000 and Windows 98)
<SYSTEMDRIVE>\Documents and Settings\
<user>\Application Data
Nonroaming stores =
Nonroaming stores =
<SYSTEMDRIVE>\Users\<user>\AppData\Local
Related Topics
TITLE DESCRIPTION
How to: Obtain Stores for Isolated Storage Provides an example of using the IsolatedStorageFile class to
obtain a store isolated by user and assembly.
How to: Enumerate Stores for Isolated Storage Shows how to use the IsolatedStorageFile.GetEnumerator
method to calculate the size of all isolated storage for the
user.
How to: Delete Stores in Isolated Storage Shows how to use the IsolatedStorageFile.Remove method in
two different ways to delete isolated stores.
TITLE DESCRIPTION
How to: Anticipate Out-of-Space Conditions with Isolated Shows how to measure the remaining space in an isolated
Storage store.
How to: Create Files and Directories in Isolated Storage Provides some examples of creating files and directories in an
isolated store.
How to: Find Existing Files and Directories in Isolated Storage Demonstrates how to read the directory structure and files in
isolated storage.
How to: Read and Write to Files in Isolated Storage Provides an example of writing a string to an isolated storage
file and reading it back.
How to: Delete Files and Directories in Isolated Storage Demonstrates how to delete isolated storage files and
directories.
File and Stream I/O Explains how you can perform synchronous and
asynchronous file and data stream access.
Reference
System.IO.IsolatedStorage.IsolatedStorage
System.IO.IsolatedStorage.IsolatedStorageFile
System.IO.IsolatedStorage.IsolatedStorageFileStream
System.IO.IsolatedStorage.IsolatedStorageScope
Types of Isolation
5 minutes to read • Edit Online
Access to isolated storage is always restricted to the user who created it. To implement this type of isolation, the
common language runtime uses the same notion of user identity that the operating system recognizes, which is
the identity associated with the process in which the code is running when the store is opened. This identity is an
authenticated user identity, but impersonation can cause the identity of the current user to change dynamically.
Access to isolated storage is also restricted according to the identity associated with the application's domain and
assembly, or with the assembly alone. The runtime obtains these identities in the following ways:
Domain identity represents the evidence of the application, which in the case of a web application might be
the full URL. For shell-hosted code, the domain identity might be based on the application directory path.
For example, if the executable runs from the path C:\Office\MyApp.exe, the domain identity would be
C:\Office\MyApp.exe.
Assembly identity is the evidence of the assembly. This might come from a cryptographic digital signature,
which can be the assembly's strong name, the software publisher of the assembly, or its URL identity. If an
assembly has both a strong name and a software publisher identity, then the software publisher identity is
used. If the assembly comes from the Internet and is unsigned, the URL identity is used. For more
information about assemblies and strong names, see Programming with Assemblies.
Roaming stores move with a user that has a roaming user profile. Files are written to a network directory
and are downloaded to any computer the user logs into. For more information about roaming user profiles,
see IsolatedStorageScope.Roaming.
By combining the concepts of user, domain, and assembly identity, isolated storage can isolate data in the
following ways, each of which has its own usage scenarios:
Isolation by user and assembly
Isolation by user, domain, and assembly
Either of these isolations can be combined with a roaming user profile. For more information, see the section
Isolated Storage and Roaming.
The following illustration demonstrates how stores are isolated in different scopes:
Note that except for roaming stores, isolated storage is always implicitly isolated by computer because it uses the
storage facilities that are local to a given computer.
IMPORTANT
Isolated storage is not available for Windows 8.x Store apps. Instead, use the application data classes in the
Windows.Storage namespaces included in the Windows Runtime API to store local data and files. For more information, see
Application data in the Windows Dev Center.
IsolatedStorageFile^ isoFile =
IsolatedStorageFile::GetStore(IsolatedStorageScope::User |
IsolatedStorageScope::Assembly, (Type^)nullptr, (Type^)nullptr);
IsolatedStorageFile isoFile =
IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly, null, null);
For an example that uses the evidence parameters, see GetStore(IsolatedStorageScope, Evidence, Type, Evidence,
Type).
The GetUserStoreForAssembly method is available as a shortcut, as shown in the following code example. This
shortcut cannot be used to open stores that are capable of roaming; use GetStore in such cases.
IsolatedStorageFile^ isoFile =
IsolatedStorageFile::GetStore(IsolatedStorageScope::User |
IsolatedStorageScope::Domain |
IsolatedStorageScope::Assembly, (Type^)nullptr, (Type^)nullptr);
IsolatedStorageFile isoFile =
IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Domain |
IsolatedStorageScope.Assembly, null, null);
Another method is available as a shortcut, as shown in the following code example. This shortcut cannot be used
to open stores that are capable of roaming; use GetStore in such cases.
IsolatedStorageFile^ isoFile =
IsolatedStorageFile::GetStore(IsolatedStorageScope::User |
IsolatedStorageScope::Assembly |
IsolatedStorageScope::Roaming, (Type^)nullptr, (Type^)nullptr);
IsolatedStorageFile isoFile =
IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly |
IsolatedStorageScope.Roaming, null, null);
A domain scope can be added to create a roaming store isolated by user, domain, and application. The following
code example demonstrates this.
IsolatedStorageFile^ isoFile =
IsolatedStorageFile::GetStore(IsolatedStorageScope::User |
IsolatedStorageScope::Assembly | IsolatedStorageScope::Domain |
IsolatedStorageScope::Roaming, (Type^)nullptr, (Type^)nullptr);
IsolatedStorageFile isoFile =
IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly | IsolatedStorageScope.Domain |
IsolatedStorageScope.Roaming, null, null);
See also
IsolatedStorageScope
Isolated Storage
How to: Obtain Stores for Isolated Storage
2 minutes to read • Edit Online
An isolated store exposes a virtual file system within a data compartment. The IsolatedStorageFile class supplies a
number of methods for interacting with an isolated store. To create and retrieve stores, IsolatedStorageFile
provides three static methods:
GetUserStoreForAssembly returns storage that is isolated by user and assembly.
GetUserStoreForDomain returns storage that is isolated by domain and assembly.
Both methods retrieve a store that belongs to the code from which they are called.
The static method GetStore returns an isolated store that is specified by passing in a combination of scope
parameters.
The following code returns a store that is isolated by user, assembly, and domain.
You can use the GetStore method to specify that a store should roam with a roaming user profile. For details on
how to set this up, see Types of Isolation.
Isolated stores obtained from within different assemblies are, by default, different stores. You can access the store
of a different assembly or domain by passing in the assembly or domain evidence in the parameters of the
GetStore method. This requires permission to access isolated storage by application domain identity. For more
information, see the GetStore method overloads.
The GetUserStoreForAssembly, GetUserStoreForDomain, and GetStore methods return an IsolatedStorageFile
object. To help you decide which isolation type is most appropriate for your situation, see Types of Isolation. When
you have an isolated storage file object, you can use the isolated storage methods to read, write, create, and delete
files and directories.
There is no mechanism that prevents code from passing an IsolatedStorageFile object to code that does not have
sufficient access to get the store itself. Domain and assembly identities and isolated storage permissions are
checked only when a reference to an IsolatedStorage object is obtained, typically in the GetUserStoreForAssembly,
GetUserStoreForDomain, or GetStore method. Protecting references to IsolatedStorageFile objects is, therefore,
the responsibility of the code that uses these references.
Example
The following code provides a simple example of a class obtaining a store that is isolated by user and assembly.
The code can be changed to retrieve a store that is isolated by user, domain, and assembly by adding
IsolatedStorageScope.Domain to the arguments that the GetStore method passes.
After you run the code, you can confirm that a store was created by typing StoreAdm /LIST at the command line.
This runs the Isolated Storage tool (Storeadm.exe) and lists all the current isolated stores for the user.
using System;
using System.IO.IsolatedStorage;
Imports System.IO.IsolatedStorage
See also
IsolatedStorageFile
IsolatedStorageScope
Isolated Storage
Types of Isolation
Assemblies in .NET
How to: Enumerate Stores for Isolated Storage
2 minutes to read • Edit Online
You can enumerate all isolated stores for the current user by using the IsolatedStorageFile.GetEnumerator static
method. This method takes an IsolatedStorageScope value and returns an IsolatedStorageFile enumerator. To
enumerate stores, you must have the IsolatedStorageFilePermission permission that specifies the
AdministerIsolatedStorageByUser value. If you call the GetEnumerator method with the User value, it returns an
array of IsolatedStorageFile objects that are defined for the current user.
Example
The following code example obtains a store that is isolated by user and assembly, creates a few files, and retrieves
those files by using the GetEnumerator method.
using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Collections;
while (allFiles.MoveNext())
{
IsolatedStorageFile storeFile = (IsolatedStorageFile)allFiles.Current;
totalsize += (long)storeFile.UsedSize;
}
Module Module1
Sub Main()
Using isoStore As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or
IsolatedStorageScope.Assembly, Nothing, Nothing)
isoStore.CreateFile("TestFileA.Txt")
isoStore.CreateFile("TestFileB.Txt")
isoStore.CreateFile("TestFileC.Txt")
isoStore.CreateFile("TestFileD.Txt")
End Using
While (allFiles.MoveNext())
Dim storeFile As IsolatedStorageFile = CType(allFiles.Current, IsolatedStorageFile)
totalsize += CType(storeFile.UsedSize, Long)
End While
End Sub
End Module
See also
IsolatedStorageFile
Isolated Storage
How to: Delete Stores in Isolated Storage
2 minutes to read • Edit Online
The IsolatedStorageFile class supplies two methods for deleting isolated storage files:
The instance method Remove() does not take any arguments and deletes the store that calls it. No
permissions are required for this operation. Any code that can access the store can delete any or all the data
inside it.
The static method Remove(IsolatedStorageScope) takes the User enumeration value, and deletes all the
stores for the user who is running the code. This operation requires IsolatedStorageFilePermission
permission for the AdministerIsolatedStorageByUser value.
Example
The following code example demonstrates the use of the static and instance Remove methods. The class obtains
two stores; one is isolated for user and assembly and the other is isolated for user, domain, and assembly. The user,
domain, and assembly store is then deleted by calling the Remove() method of the isolated storage file isoStore1 .
Then, all remaining stores for the user are deleted by calling the static method Remove(IsolatedStorageScope).
using namespace System;
using namespace System::IO::IsolatedStorage;
isoStore1->Remove();
Console::WriteLine("The user, domain, and assembly isolated store has been deleted.");
// This static method deletes all the isolated stores for this user.
IsolatedStorageFile::Remove(IsolatedStorageScope::User);
Console::WriteLine("All isolated stores for this user have been deleted.");
} // End of Main.
};
int main()
{
DeletingStores::Main();
}
using System;
using System.IO.IsolatedStorage;
isoStore1.Remove();
Console.WriteLine("The user, domain, and assembly isolated store has been deleted.");
// This static method deletes all the isolated stores for this user.
IsolatedStorageFile.Remove(IsolatedStorageScope.User);
Console.WriteLine("All isolated stores for this user have been deleted.");
} // End of Main.
}
Imports System.IO.IsolatedStorage
' The Remove method deletes a specific store, in this case the
' isoStore1 file.
isoStore1.Remove()
Console.WriteLine("The user, domain, and assembly isolated store has been deleted.")
' This static method deletes all the isolated stores for this user.
IsolatedStorageFile.Remove(IsolatedStorageScope.User)
Console.WriteLine("All isolated stores for this user have been deleted.")
End Sub
End Class
See also
IsolatedStorageFile
Isolated Storage
How to: Anticipate Out-of-Space Conditions with
Isolated Storage
2 minutes to read • Edit Online
Code that uses isolated storage is constrained by a quota that specifies the maximum size for the data
compartment in which isolated storage files and directories exist. The quota is defined by security policy and is
configurable by administrators. If the maximum allowed size is exceeded when you try to write data, an
IsolatedStorageException exception is thrown and the operation fails. This helps prevent malicious denial-of-
service attacks that could cause the application to refuse requests because data storage is filled.
To help you determine whether a given write attempt is likely to fail for this reason, the IsolatedStorage class
provides three read-only properties: AvailableFreeSpace, UsedSize, and Quota. You can use these properties to
determine whether writing to the store will cause the maximum allowed size of the store to be exceeded. Keep in
mind that isolated storage can be accessed concurrently; therefore, when you compute the amount of remaining
storage, the storage space could be consumed by the time you try to write to the store. However, you can use the
maximum size of the store to help determine whether the upper limit on available storage is about to be reached.
The Quota property depends on evidence from the assembly to work properly. For this reason, you should retrieve
this property only on IsolatedStorageFile objects that were created by using the GetUserStoreForAssembly,
GetUserStoreForDomain, or GetStore method. IsolatedStorageFile objects that were created in any other way (for
example, objects that were returned from the GetEnumerator method) will not return an accurate maximum size.
Example
The following code example obtains an isolated store, creates a few files, and retrieves the AvailableFreeSpace
property. The remaining space is reported in bytes.
using namespace System;
using namespace System::IO;
using namespace System::IO::IsolatedStorage;
int main()
{
CheckingSpace::Main();
}
using System;
using System.IO;
using System.IO.IsolatedStorage;
See also
IsolatedStorageFile
Isolated Storage
How to: Obtain Stores for Isolated Storage
How to: Create Files and Directories in Isolated
Storage
2 minutes to read • Edit Online
After you have obtained an isolated store, you can create directories and files for storing data. Within a store, file
and directory names are specified with respect to the root of the virtual file system.
To create a directory, use the IsolatedStorageFile.CreateDirectory instance method. If you specify a subdirectory of
an directory that doesn't exist, both directories are created. If you specify a directory that already exists, the method
returns without creating a directory, and no exception is thrown. However, if you specify a directory name that
contains invalid characters, an IsolatedStorageException exception is thrown.
To create a file, use the IsolatedStorageFile.CreateFile method.
In the Windows operating system, isolated storage file and directory names are case-insensitive. That is, if you
create a file named ThisFile.txt , and then create another file named THISFILE.TXT , only one file is created. The
file name keeps its original casing for display purposes.
Example
The following code example illustrates how to create files and directories in an isolated store.
using System;
using System.IO;
using System.IO.IsolatedStorage;
isoStore.CreateFile("InTheRoot.txt");
Console.WriteLine("Created a new file in the root.");
isoStore.CreateFile("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt");
Console.WriteLine("Created a new file in the InsideDirectory.");
}
}
}
Imports System.IO
Imports System.IO.IsolatedStorage
Module Module1
Sub Main()
Using isoStore As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or
IsolatedStorageScope.Assembly Or IsolatedStorageScope.Domain, Nothing, Nothing)
isoStore.CreateDirectory("TopLevelDirectory")
isoStore.CreateDirectory("TopLevelDirectory/SecondLevel")
isoStore.CreateDirectory("AnotherTopLevelDirectory/InsideDirectory")
Console.WriteLine("Created directories.")
isoStore.CreateFile("InTheRoot.txt")
Console.WriteLine("Created a new file in the root.")
isoStore.CreateFile("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt")
Console.WriteLine("Created a new file in the InsideDirectory.")
End Using
End Sub
End Module
See also
IsolatedStorageFile
IsolatedStorageFileStream
Isolated Storage
How to: Find Existing Files and Directories in Isolated
Storage
6 minutes to read • Edit Online
To search for a directory in isolated storage, use the IsolatedStorageFile.GetDirectoryNames method. This method
takes a string that represents a search pattern. You can use both single-character (?) and multi-character (*)
wildcard characters in the search pattern, but the wildcard characters must appear in the final portion of the name.
For example, directory1/*ect* is a valid search string, but *ect*/directory2 is not.
To search for a file, use the IsolatedStorageFile.GetFileNames method. The restriction for wildcard characters in
search strings that applies to GetDirectoryNames also applies to GetFileNames.
Neither of these methods is recursive; the IsolatedStorageFile class does not supply any methods for listing all
directories or files in your store. However, recursive methods are shown in the following code example.
Example
The following code example illustrates how to create files and directories in an isolated store. First, a store that is
isolated for user, domain, and assembly is retrieved and placed in the isoStore variable. The CreateDirectory
method is used to set up a few different directories, and the IsolatedStorageFileStream(String, FileMode,
IsolatedStorageFile) constructor creates some files in these directories. The code then loops through the results of
the GetAllDirectories method. This method uses GetDirectoryNames to find all the directory names in the
current directory. These names are stored in an array, and then GetAllDirectories calls itself, passing in each
directory it has found. As a result, all the directory names are returned in an array. Next, the code calls the
GetAllFiles method. This method calls GetAllDirectories to find out the names of all the directories, and then it
checks each directory for files by using the GetFileNames method. The result is returned in an array for display.
Console::WriteLine('\r');
Console::WriteLine("Here is a list of all directories in this isolated store:");
} // End of Main.
if (root != "")
{
root += "/";
}
// Retrieve directories.
array<String^>^ directories = storeFile->GetDirectoryNames(pattern);
return directoryList;
}
return fileList;
} // End of GetFiles.
};
int main()
{
FindingExistingFilesAndDirectories::Main();
}
using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Collections;
using System.Collections.Generic;
Console.WriteLine('\r');
Console.WriteLine("Here is a list of all directories in this isolated store:");
} // End of Main.
if (root != "")
{
root += "/";
}
// Retrieve directories.
List<String> directoryList = new List<String>(storeFile.GetDirectoryNames(pattern));
List<String> directoryList = new List<String>(storeFile.GetDirectoryNames(pattern));
return directoryList;
}
return fileList;
} // End of GetFiles.
}
Imports System.IO
Imports System.IO.IsolatedStorage
Imports System.Collections
Imports System.Collections.Generic
Console.WriteLine()
Console.WriteLine("Here is a list of all directories in this isolated store:")
GetAllDirectories("*", isoStore)
For Each directory As String In directoryList
Console.WriteLine(directory)
Next
Console.WriteLine()
Console.WriteLine("Retrieve all the files in the directory by calling the GetFiles method.")
GetAllFiles(isoStore)
For Each file As String In fileList
Console.WriteLine(file)
Next
End Sub
To read from, or write to, a file in an isolated store, use an IsolatedStorageFileStream object with a stream reader
(StreamReader object) or stream writer (StreamWriter object).
Example
The following code example obtains an isolated store and checks whether a file named TestStore.txt exists in the
store. If it doesn't exist, it creates the file and writes "Hello Isolated Storage" to the file. If TestStore.txt already exists,
the example code reads from the file.
using System;
using System.IO;
using System.IO.IsolatedStorage;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly, null, null);
if (isoStore.FileExists("TestStore.txt"))
{
Console.WriteLine("The file already exists!");
using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream("TestStore.txt",
FileMode.Open, isoStore))
{
using (StreamReader reader = new StreamReader(isoStream))
{
Console.WriteLine("Reading contents:");
Console.WriteLine(reader.ReadToEnd());
}
}
}
else
{
using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream("TestStore.txt",
FileMode.CreateNew, isoStore))
{
using (StreamWriter writer = new StreamWriter(isoStream))
{
writer.WriteLine("Hello Isolated Storage");
Console.WriteLine("You have written to the file.");
}
}
}
}
}
}
Imports System.IO
Imports System.IO.IsolatedStorage
Module Module1
Sub Main()
Dim isoStore As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or
IsolatedStorageScope.Assembly, Nothing, Nothing)
If (isoStore.FileExists("TestStore.txt")) Then
Console.WriteLine("The file already exists!")
Using isoStream As IsolatedStorageFileStream = New IsolatedStorageFileStream("TestStore.txt",
FileMode.Open, isoStore)
Using reader As StreamReader = New StreamReader(isoStream)
Console.WriteLine("Reading contents:")
Console.WriteLine(reader.ReadToEnd())
End Using
End Using
Else
Using isoStream As IsolatedStorageFileStream = New IsolatedStorageFileStream("TestStore.txt",
FileMode.CreateNew, isoStore)
Using writer As StreamWriter = New StreamWriter(isoStream)
writer.WriteLine("Hello Isolated Storage")
Console.WriteLine("You have written to the file.")
End Using
End Using
End If
End Sub
End Module
See also
IsolatedStorageFile
IsolatedStorageFileStream
System.IO.FileMode
System.IO.FileAccess
System.IO.StreamReader
System.IO.StreamWriter
File and Stream I/O
Isolated Storage
How to: Delete Files and Directories in Isolated
Storage
3 minutes to read • Edit Online
You can delete directories and files within an isolated storage file. Within a store, file and directory names are
operating-system dependent and are specified as relative to the root of the virtual file system. They are not case-
sensitive on Windows operating systems.
The System.IO.IsolatedStorage.IsolatedStorageFile class supplies two methods for deleting directories and files:
DeleteDirectory and DeleteFile. An IsolatedStorageException exception is thrown if you try to delete a file or
directory that does not exist. If you include a wildcard character in the name, DeleteDirectory throws an
IsolatedStorageException exception, and DeleteFile throws an ArgumentException exception.
The DeleteDirectory method fails if the directory contains any files or subdirectories. You can use the
GetFileNames and GetDirectoryNames methods to retrieve the existing files and directories. For more information
about searching the virtual file system of a store, see How to: Find Existing Files and Directories in Isolated
Storage.
Example
The following code example creates and then deletes several directories and files.
Console::WriteLine("Creating Directories:");
isoStore->CreateDirectory("TopLevelDirectory");
Console::WriteLine("TopLevelDirectory");
isoStore->CreateDirectory("TopLevelDirectory/SecondLevel");
Console::WriteLine("TopLevelDirectory/SecondLevel");
// This code creates two new directories, one inside the other.
isoStore->CreateDirectory("AnotherTopLevelDirectory/InsideDirectory");
Console::WriteLine("AnotherTopLevelDirectory/InsideDirectory");
Console::WriteLine();
// This code creates a few files and places them in the directories.
Console::WriteLine("Creating Files:");
isoStream1->Close();
isoStream2->Close();
Console::WriteLine("Deleting File:");
Console::WriteLine("Deleting Directory:");
isoStore->DeleteDirectory("AnotherTopLevelDirectory/InsideDirectory/");
Console::WriteLine("AnotherTopLevelDirectory/InsideDirectory/");
Console::WriteLine();
} // End of main.
};
int main()
{
DeletingFilesDirectories::Main();
}
using System;
using System.IO.IsolatedStorage;
using System.IO;
Console.WriteLine("Creating Directories:");
isoStore.CreateDirectory("TopLevelDirectory");
Console.WriteLine("TopLevelDirectory");
isoStore.CreateDirectory("TopLevelDirectory/SecondLevel");
Console.WriteLine("TopLevelDirectory/SecondLevel");
// This code creates two new directories, one inside the other.
isoStore.CreateDirectory("AnotherTopLevelDirectory/InsideDirectory");
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory");
Console.WriteLine();
// This code creates a few files and places them in the directories.
Console.WriteLine("Creating Files:");
isoStream1.Close();
isoStream2.Close();
Console.WriteLine("Deleting File:");
Console.WriteLine("Deleting Directory:");
isoStore.DeleteDirectory("AnotherTopLevelDirectory/InsideDirectory/");
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory/");
Console.WriteLine();
} // End of main.
}
Imports System.IO.IsolatedStorage
Imports System.IO
Console.WriteLine("Creating Directories:")
isoStore.CreateDirectory("TopLevelDirectory")
Console.WriteLine("TopLevelDirectory")
isoStore.CreateDirectory("TopLevelDirectory/SecondLevel")
Console.WriteLine("TopLevelDirectory/SecondLevel")
' This code creates two new directories, one inside the other.
isoStore.CreateDirectory("AnotherTopLevelDirectory/InsideDirectory")
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory")
Console.WriteLine()
' This code creates a few files and places them in the directories.
Console.WriteLine("Creating Files:")
isoStream1.Close()
isoStream2.Close()
Console.WriteLine("Deleting File:")
Console.WriteLine("Deleting Directory:")
isoStore.DeleteDirectory("AnotherTopLevelDirectory/InsideDirectory/")
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory/")
Console.WriteLine()
End Sub
End Class
See also
System.IO.IsolatedStorage.IsolatedStorageFile
Isolated Storage
Pipe Operations in .NET
2 minutes to read • Edit Online
Pipes provide a means for interprocess communication. There are two types of pipes:
Anonymous pipes.
Anonymous pipes provide interprocess communication on a local computer. Anonymous pipes require less
overhead than named pipes but offer limited services. Anonymous pipes are one-way and cannot be used
over a network. They support only a single server instance. Anonymous pipes are useful for communication
between threads, or between parent and child processes where the pipe handles can be easily passed to the
child process when it is created.
In .NET, you implement anonymous pipes by using the AnonymousPipeServerStream and
AnonymousPipeClientStream classes.
See How to: Use Anonymous Pipes for Local Interprocess Communication.
Named pipes.
Named pipes provide interprocess communication between a pipe server and one or more pipe clients.
Named pipes can be one-way or duplex. They support message-based communication and allow multiple
clients to connect simultaneously to the server process using the same pipe name. Named pipes also
support impersonation, which enables connecting processes to use their own permissions on remote
servers.
In .NET, you implement named pipes by using the NamedPipeServerStream and NamedPipeClientStream
classes.
See How to: Use Named Pipes for Network Interprocess Communication.
See also
File and Stream I/O
How to: Use Anonymous Pipes for Local Interprocess Communication
How to: Use Named Pipes for Network Interprocess Communication
How to: Use Anonymous Pipes for Local Interprocess
Communication
4 minutes to read • Edit Online
Anonymous pipes provide interprocess communication on a local computer. They offer less functionality than
named pipes, but also require less overhead. You can use anonymous pipes to make interprocess communication
on a local computer easier. You cannot use anonymous pipes for communication over a network.
To implement anonymous pipes, use the AnonymousPipeServerStream and AnonymousPipeClientStream classes.
Example
The following example demonstrates a way to send a string from a parent process to a child process using
anonymous pipes. This example creates an AnonymousPipeServerStream object in a parent process with a
PipeDirection value of Out. The parent process then creates a child process by using a client handle to create an
AnonymousPipeClientStream object. The child process has a PipeDirection value of In.
The parent process then sends a user-supplied string to the child process. The string is displayed to the console in
the child process.
The following example shows the server process.
#using <System.dll>
#using <System.Core.dll>
pipeClient->StartInfo->FileName = "pipeClient.exe";
AnonymousPipeServerStream^ pipeServer =
gcnew AnonymousPipeServerStream(PipeDirection::Out,
HandleInheritability::Inheritable);
pipeServer->DisposeLocalCopyOfClientHandle();
try
{
// Read user input and send that to the client process.
StreamWriter^ sw = gcnew StreamWriter(pipeServer);
sw->AutoFlush = true;
// Send a 'sync message' and wait for client to receive it.
sw->WriteLine("SYNC");
pipeServer->WaitForPipeDrain();
// Send the console input to the client process.
Console::Write("[SERVER] Enter text: ");
sw->WriteLine(Console::ReadLine());
sw->Close();
}
// Catch the IOException that is raised if the pipe is broken
// or disconnected.
catch (IOException^ e)
{
Console::WriteLine("[SERVER] Error: {0}", e->Message);
}
pipeServer->Close();
pipeClient->WaitForExit();
pipeClient->Close();
Console::WriteLine("[SERVER] Client quit. Server terminating.");
}
};
int main()
{
PipeServer::Main();
}
using System;
using System.IO;
using System.IO.Pipes;
using System.Diagnostics;
class PipeServer
{
static void Main()
{
Process pipeClient = new Process();
pipeClient.StartInfo.FileName = "pipeClient.exe";
pipeServer.DisposeLocalCopyOfClientHandle();
try
{
// Read user input and send that to the client process.
using (StreamWriter sw = new StreamWriter(pipeServer))
{
sw.AutoFlush = true;
// Send a 'sync message' and wait for client to receive it.
sw.WriteLine("SYNC");
pipeServer.WaitForPipeDrain();
// Send the console input to the client process.
Console.Write("[SERVER] Enter text: ");
sw.WriteLine(Console.ReadLine());
}
}
// Catch the IOException that is raised if the pipe is broken
// or disconnected.
catch (IOException e)
{
Console.WriteLine("[SERVER] Error: {0}", e.Message);
}
}
pipeClient.WaitForExit();
pipeClient.Close();
Console.WriteLine("[SERVER] Client quit. Server terminating.");
}
}
Imports System.IO
Imports System.IO.Pipes
Imports System.Diagnostics
Class PipeServer
Shared Sub Main()
Dim pipeClient As New Process()
pipeClient.StartInfo.FileName = "pipeClient.exe"
pipeServer.DisposeLocalCopyOfClientHandle()
Try
' Read user input and send that to the client process.
Using sw As New StreamWriter(pipeServer)
sw.AutoFlush = true
' Send a 'sync message' and wait for client to receive it.
sw.WriteLine("SYNC")
pipeServer.WaitForPipeDrain()
' Send the console input to the client process.
Console.Write("[SERVER] Enter text: ")
sw.WriteLine(Console.ReadLine())
End Using
Catch e As IOException
' Catch the IOException that is raised if the pipe is broken
' or disconnected.
Console.WriteLine("[SERVER] Error: {0}", e.Message)
End Try
End Using
pipeClient.WaitForExit()
pipeClient.Close()
Console.WriteLine("[SERVER] Client quit. Server terminating.")
End Sub
End Class
Example
The following example shows the client process. The server process starts the client process and gives that process
a client handle. The resulting executable from the client code should be named pipeClient.exe and be copied to
the same directory as the server executable before running the server process.
#using <System.Core.dll>
int main()
{
array<String^>^ args = Environment::GetCommandLineArgs();
PipeClient::Main(args);
}
using System;
using System.IO;
using System.IO.Pipes;
class PipeClient
{
static void Main(string[] args)
{
if (args.Length > 0)
{
using (PipeStream pipeClient =
new AnonymousPipeClientStream(PipeDirection.In, args[0]))
{
Console.WriteLine("[CLIENT] Current TransmissionMode: {0}.",
pipeClient.TransmissionMode);
Class PipeClient
Shared Sub Main(args() as String)
If args.Length > 0 Then
Using pipeClient As New AnonymousPipeClientStream(PipeDirection.In, args(0))
Console.WriteLine("[CLIENT] Current TransmissionMode: {0}.", _
pipeClient.TransmissionMode)
See also
Pipes
How to: Use Named Pipes for Network Interprocess Communication
How to: Use Named Pipes for Network Interprocess
Communication
11 minutes to read • Edit Online
Named pipes provide interprocess communication between a pipe server and one or more pipe clients. They offer
more functionality than anonymous pipes, which provide interprocess communication on a local computer.
Named pipes support full duplex communication over a network and multiple server instances, message-based
communication, and client impersonation, which enables connecting processes to use their own set of permissions
on remote servers.
To implement name pipes, use the NamedPipeServerStream and NamedPipeClientStream classes.
Example
The following example demonstrates how to create a named pipe by using the NamedPipeServerStream class. In
this example, the server process creates four threads. Each thread can accept a client connection. The connected
client process then supplies the server with a file name. If the client has sufficient permissions, the server process
opens the file and sends its contents back to the client.
#using <System.Core.dll>
// Defines the data protocol for reading and writing strings on our stream
public ref class StreamString
{
private:
Stream^ ioStream;
UnicodeEncoding^ streamEncoding;
public:
StreamString(Stream^ ioStream)
{
this->ioStream = ioStream;
streamEncoding = gcnew UnicodeEncoding();
}
String^ ReadString()
{
int len;
return streamEncoding->GetString(inBuffer);
}
return outBuffer->Length + 2;
}
};
public:
ReadFileToStream(StreamString^ str, String^ filename)
{
fn = filename;
ss = str;
}
void Start()
{
String^ contents = File::ReadAllText(fn);
ss->WriteString(contents);
}
};
public:
static void Main()
{
int i;
array<Thread^>^ servers = gcnew array<Thread^>(numThreads);
private:
static void ServerThread()
{
NamedPipeServerStream^ pipeServer =
gcnew NamedPipeServerStream("testpipe", PipeDirection::InOut, numThreads);
int main()
{
PipeServer::Main();
}
using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;
// Defines the data protocol for reading and writing strings on our stream
public class StreamString
{
{
private Stream ioStream;
private UnicodeEncoding streamEncoding;
return streamEncoding.GetString(inBuffer);
}
return outBuffer.Length + 2;
}
}
Imports System.IO
Imports System.IO.Pipes
Imports System.Text
Imports System.Threading
Console.WriteLine(vbNewLine + "*** Named pipe server stream with impersonation example ***" +
vbNewLine)
Console.WriteLine("Waiting for client connect..." + vbNewLine)
For i = 0 To numThreads - 1
servers(i) = New Thread(AddressOf ServerThread)
servers(i).Start()
Next i
Thread.Sleep(250)
While i > 0
For j As Integer = 0 To numThreads - 1
If Not(servers(j) Is Nothing) Then
if servers(j).Join(250)
Console.WriteLine("Server thread[{0}] finished.", servers(j).ManagedThreadId)
servers(j) = Nothing
i -= 1 ' decrement the thread watch count
End If
End If
Next j
End While
Console.WriteLine(vbNewLine + "Server threads exhausted, exiting.")
End Sub
' Read in the contents of the file while impersonating the client.
Dim fileReader As New ReadFileToStream(ss, filename)
' Defines the data protocol for reading and writing strings on our stream
Public Class StreamString
Private ioStream As Stream
Private streamEncoding As UnicodeEncoding
Return streamEncoding.GetString(inBuffer)
End Function
Return outBuffer.Length + 2
End Function
End Class
' Contains the method executed in the context of the impersonated user
Public Class ReadFileToStream
Private fn As String
Private ss As StreamString
Example
The following example shows the client process, which uses the NamedPipeClientStream class. The client
connects to the server process and sends a file name to the server. The example uses impersonation, so the
identity that is running the client application must have permission to access the file. The server then sends the
contents of the file back to the client. The file contents are then displayed to the console.
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Security.Principal;
using System.Text;
using System.Threading;
Console.WriteLine("Connecting to server...\n");
pipeClient.Connect();
if (currentProcessName.Contains(Environment.CurrentDirectory))
{
currentProcessName = currentProcessName.Replace(Environment.CurrentDirectory, String.Empty);
}
int i;
for (i = 0; i < numClients; i++)
{
// Start 'this' program but spawn a named pipe client.
plist[i] = Process.Start(currentProcessName, "spawnclient");
}
while (i > 0)
{
for (int j = 0; j < numClients; j++)
{
if (plist[j] != null)
if (plist[j] != null)
{
if (plist[j].HasExited)
{
Console.WriteLine($"Client process[{plist[j].Id}] has exited.");
plist[j] = null;
i--; // decrement the process watch count
}
else
{
Thread.Sleep(250);
}
}
}
}
Console.WriteLine("\nClient processes finished, exiting.");
}
}
// Defines the data protocol for reading and writing strings on our stream.
public class StreamString
{
private Stream ioStream;
private UnicodeEncoding streamEncoding;
return streamEncoding.GetString(inBuffer);
}
return outBuffer.Length + 2;
}
}
Imports System.Diagnostics
Imports System.IO
Imports System.IO.Pipes
Imports System.Security.Principal
Imports System.Text
Imports System.Threading
If currentProcessName.Contains(Environment.CurrentDirectory) Then
currentProcessName = currentProcessName.Replace(Environment.CurrentDirectory, String.Empty)
End If
' Change extension for .NET Core "dotnet run" returns the DLL, not the host exe.
currentProcessName = Path.ChangeExtension(currentProcessName, ".exe")
Dim i As Integer
For i = 0 To numClients - 1
' Start 'this' program but spawn a named pipe client.
plist(i) = Process.Start(currentProcessName, "spawnclient")
Next
While i > 0
For j As Integer = 0 To numClients - 1
If plist(j) IsNot Nothing Then
If plist(j).HasExited Then
Console.WriteLine($"Client process[{plist(j).Id}] has exited.")
plist(j) = Nothing
i -= 1 ' decrement the process watch count
Else
Thread.Sleep(250)
End If
End If
Next
End While
Console.WriteLine(vbNewLine + "Client processes finished, exiting.")
End Sub
End Class
' Defines the data protocol for reading and writing strings on our stream
Public Class StreamString
Private ioStream As Stream
Private streamEncoding As UnicodeEncoding
Return streamEncoding.GetString(inBuffer)
End Function
Return outBuffer.Length + 2
End Function
End Class
Robust Programming
The client and server processes in this example are intended to run on the same computer, so the server name
provided to the NamedPipeClientStream object is "." . If the client and server processes were on separate
computers, "." would be replaced with the network name of the computer that runs the server process.
See also
TokenImpersonationLevel
GetImpersonationUserName
Pipes
How to: Use Anonymous Pipes for Local Interprocess Communication
System.IO.Pipelines in .NET
19 minutes to read • Edit Online
System.IO.Pipelines is a new library that is designed to make it easier to do high-performance I/O in .NET. It’s a
library targeting .NET Standard that works on all .NET implementations.
while (true)
{
// Calculate the amount of bytes remaining in the buffer.
var bytesRemaining = buffer.Length - bytesBuffered;
if (bytesRemaining == 0)
{
// Double the buffer size and copy the previously buffered data into the new buffer.
var newBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length * 2);
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
// Return the old buffer to the pool.
ArrayPool<byte>.Shared.Return(buffer);
buffer = newBuffer;
bytesRemaining = buffer.Length - bytesBuffered;
}
do
{
// Look for a EOL in the buffered data.
linePosition = Array.IndexOf(buffer, (byte)'\n', bytesConsumed,
bytesBuffered - bytesConsumed);
if (linePosition >= 0)
{
// Calculate the length of the line based on the offset.
var lineLength = linePosition - bytesConsumed;
// Move the bytesConsumed to skip past the line consumed (including \n).
bytesConsumed += lineLength + 1;
}
}
while (linePosition >= 0);
}
}
The previous code is complex and doesn't address all the problems identified. High-performance networking
usually means writing very complex code to maximize performance. System.IO.Pipelines was designed to make
writing this type of code easier.
Pipe
The Pipe class can be used to create a PipeWriter/PipeReader pair. All data written into the PipeWriter is available
in the PipeReader :
var pipe = new Pipe();
PipeReader reader = pipe.Reader;
PipeWriter writer = pipe.Writer;
while (true)
{
// Allocate at least 512 bytes from the PipeWriter.
Memory<byte> memory = writer.GetMemory(minimumBufferSize);
try
{
int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None);
if (bytesRead == 0)
{
break;
}
// Tell the PipeWriter how much was read from the Socket.
writer.Advance(bytesRead);
}
catch (Exception ex)
{
LogError(ex);
break;
}
if (result.IsCompleted)
{
break;
}
}
// By completing PipeWriter, tell the PipeReader that there's no more data coming.
await writer.CompleteAsync();
}
// Tell the PipeReader how much of the buffer has been consumed.
reader.AdvanceTo(buffer.Start, buffer.End);
reader.AdvanceTo(buffer.Start, buffer.End);
if (position == null)
{
line = default;
return false;
}
There are no explicit buffers allocated. All buffer management is delegated to the PipeReader and PipeWriter
implementations. Delegating buffer management makes it easier for consuming code to focus solely on the
business logic.
In the first loop:
PipeWriter.GetMemory(Int32) is called to get memory from the underlying writer.
PipeWriter.Advance(Int32) is called to tell the PipeWriter how much data was written to the buffer.
PipeWriter.FlushAsync is called to make the data available to the PipeReader .
In the second loop, the PipeReader consumes the buffers written by PipeWriter . The buffers come from the
socket. The call to PipeReader.ReadAsync :
Returns a ReadResult that contains two important pieces of information:
The data that was read in the form of ReadOnlySequence<byte> .
A boolean IsCompleted that indicates if the end of data (EOF ) has been reached.
After finding the end of line (EOL ) delimiter and parsing the line:
The logic processes the buffer to skip what's already processed.
PipeReader.AdvanceTo is called to tell the PipeReader how much data has been consumed and examined.
The reader and writer loops end by calling Complete . Complete lets the underlying Pipe release the memory it
allocated.
Backpressure and flow control
Ideally, reading and parsing work together:
The writing thread consumes data from the network and puts it in buffers.
The parsing thread is responsible for constructing the appropriate data structures.
Typically, parsing takes more time than just copying blocks of data from the network:
The reading thread gets ahead of the parsing thread.
The reading thread has to either slow down or allocate more memory to store the data for the parsing thread.
For optimal performance, there's a balance between frequent pauses and allocating more memory.
To solve the preceding problem, the Pipe has two settings to control the flow of data:
PauseWriterThreshold: Determines how much data should be buffered before calls to FlushAsync pause.
ResumeWriterThreshold: Determines how much data the reader has to observe before calls to
PipeWriter.FlushAsync resume.
PipeWriter.FlushAsync:
Returns an incomplete ValueTask<FlushResult> when the amount of data in the Pipe crosses
PauseWriterThreshold .
Completes ValueTask<FlushResult> when it becomes lower than ResumeWriterThreshold .
Two values are used to prevent rapid cycling, which can occur if one value is used.
Examples
// The Pipe will start returning incomplete tasks from FlushAsync until
// the reader examines at least 5 bytes.
var options = new PipeOptions(pauseWriterThreshold: 10, resumeWriterThreshold: 5);
var pipe = new Pipe(options);
PipeScheduler
Typically when using async and await , asynchronous code resumes on either on a TaskScheduler or on the
current SynchronizationContext.
When doing I/O, it's important to have fine-grained control over where the I/O is performed. This control allows
taking advantage of CPU caches effectively. Efficient caching is critical for high-performance apps like web servers.
PipeScheduler provides control over where asynchronous callbacks run. By default:
The current SynchronizationContext is used.
If there's no SynchronizationContext , it uses the thread pool to run callbacks.
public static void Main(string[] args)
{
var writeScheduler = new SingleThreadPipeScheduler();
var readScheduler = new SingleThreadPipeScheduler();
// Tell the Pipe what schedulers to use and disable the SynchronizationContext.
var options = new PipeOptions(readerScheduler: readScheduler,
writerScheduler: writeScheduler,
useSynchronizationContext: false);
var pipe = new Pipe(options);
}
public SingleThreadPipeScheduler()
{
_thread = new Thread(DoWork);
_thread.Start();
}
PipeScheduler.ThreadPool is the PipeScheduler implementation that queues callbacks to the thread pool.
PipeScheduler.ThreadPool is the default and generally the best choice. PipeScheduler.Inline can cause unintended
consequences such as deadlocks.
Pipe reset
It's frequently efficient to reuse the Pipe object. To reset the pipe, call PipeReader Reset when both the
PipeReader and PipeWriter are complete.
PipeReader
PipeReader manages memory on the caller's behalf. Always call PipeReader.AdvanceTo after calling
PipeReader.ReadAsync. This lets the PipeReader know when the caller is done with the memory so that it can be
tracked. The ReadOnlySequence<byte> returned from PipeReader.ReadAsync is only valid until the call the
PipeReader.AdvanceTo . It's illegal to use ReadOnlySequence<byte> after calling PipeReader.AdvanceTo .
try
{
if (TryParseMessage(ref buffer, out Message message))
{
// A single message was successfully parsed so mark the start as the
// parsed buffer as consumed. TryParseMessage trims the buffer to
// point to the data after the message was parsed.
consumed = buffer.Start;
return message;
}
break;
}
}
finally
{
reader.AdvanceTo(consumed, examined);
}
}
return null;
}
try
{
// Process all messages from the buffer, modifying the input buffer on each
// iteration.
while (TryParseMessage(ref buffer, out Message message))
{
await ProcessMessageAsync(message);
}
Cancellation
PipeReader.ReadAsync :
Supports passing a CancellationToken.
Throws an OperationCanceledException if the CancellationToken is canceled while there's a read pending.
Supports a way to cancel the current read operation via PipeReader.CancelPendingRead, which avoids raising
an exception. Calling PipeReader.CancelPendingRead causes the current or next call to PipeReader.ReadAsync to
return a ReadResult with IsCanceled set to true . This can be useful for halting the existing read loop in a non-
destructive and non-exceptional way.
private PipeReader reader;
try
{
if (result.IsCanceled)
{
// The read was canceled. You can quit without reading the existing data.
break;
}
// Process all messages from the buffer, modifying the input buffer on each
// iteration.
while (TryParseMessage(ref buffer, out Message message))
{
await ProcessMessageAsync(message);
}
Passing the wrong values to consumed or examined may result in infinite buffering (eventual OOM ).
Using the ReadOnlySequence<byte> after calling PipeReader.AdvanceTo may result in memory corruption (use
after free).
Failing to call PipeReader.Complete/CompleteAsync may result in a memory leak.
Checking ReadResult.IsCompleted and exiting the reading logic before processing the buffer results in data
loss. The loop exit condition should be based on ReadResult.Buffer.IsEmpty and ReadResult.IsCompleted .
Doing this incorrectly could result in an infinite loop.
Problematic code
Data loss
The ReadResult can return the final segment of data when IsCompleted is set to true . Not reading that data
before exiting the read loop will result in data loss.
WARNING
Do NOT use the following code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The following sample is provided to explain PipeReader Common problems.
if (result.IsCompleted)
{
break;
}
reader.AdvanceTo(dataLossBuffer.Start, dataLossBuffer.End);
}
WARNING
Do NOT use the preceding code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The preceding sample is provided to explain PipeReader Common problems.
Infinite loop
The following logic may result in an infinite loop if the Result.IsCompleted is true but there's never a complete
message in the buffer.
WARNING
Do NOT use the following code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The following sample is provided to explain PipeReader Common problems.
Environment.FailFast("This code is terrible, don't use it!");
while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> infiniteLoopBuffer = result.Buffer;
if (result.IsCompleted && infiniteLoopBuffer.IsEmpty)
{
break;
}
reader.AdvanceTo(infiniteLoopBuffer.Start, infiniteLoopBuffer.End);
}
WARNING
Do NOT use the preceding code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The preceding sample is provided to explain PipeReader Common problems.
Here's another piece of code with the same problem. It's checking for a non-empty buffer before checking
ReadResult.IsCompleted . Because it's in an else if , it will loop forever if there's never a complete message in the
buffer.
WARNING
Do NOT use the following code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The following sample is provided to explain PipeReader Common problems.
if (!infiniteLoopBuffer.IsEmpty)
{
Process(ref infiniteLoopBuffer, out Message message);
}
else if (result.IsCompleted)
{
break;
}
reader.AdvanceTo(infiniteLoopBuffer.Start, infiniteLoopBuffer.End);
}
WARNING
Do NOT use the preceding code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The preceding sample is provided to explain PipeReader Common problems.
Unexpected Hang
Unconditionally calling PipeReader.AdvanceTo with buffer.End in the examined position may result in hangs when
parsing a single message. The next call to PipeReader.AdvanceTo won't return until:
There's more data written to the pipe.
And the new data wasn't previously examined.
WARNING
Do NOT use the following code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The following sample is provided to explain PipeReader Common problems.
if (result.IsCompleted)
{
break;
}
reader.AdvanceTo(hangBuffer.Start, hangBuffer.End);
if (message != null)
{
return message;
}
}
WARNING
Do NOT use the preceding code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The preceding sample is provided to explain PipeReader Common problems.
WARNING
Do NOT use the following code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The following sample is provided to explain PipeReader Common problems.
Environment.FailFast("This code is terrible, don't use it!");
while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> thisCouldOutOfMemory = result.Buffer;
if (result.IsCompleted)
{
break;
}
reader.AdvanceTo(thisCouldOutOfMemory.Start, thisCouldOutOfMemory.End);
if (message != null)
{
return message;
}
}
WARNING
Do NOT use the preceding code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The preceding sample is provided to explain PipeReader Common problems.
Memory Corruption
When writing helpers that read the buffer, any returned payload should be copied before calling Advance . The
following example will return memory that the Pipe has discarded and may reuse it for the next operation
(read/write).
WARNING
Do NOT use the following code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The following sample is provided to explain PipeReader Common problems.
while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> buffer = result.Buffer;
buffer = buffer.Slice(length);
}
if (result.IsCompleted)
{
break;
}
reader.AdvanceTo(buffer.Start, buffer.End);
if (message != null)
{
// This code is broken since reader.AdvanceTo() was called with a position *after* the buffer
// was captured.
break;
}
}
return message;
}
WARNING
Do NOT use the preceding code. Using this sample will result in data loss, hangs, security issues and should NOT be copied.
The preceding sample is provided to explain PipeReader Common problems.
PipeWriter
The PipeWriter manages buffers for writing on the caller's behalf. PipeWriter implements IBufferWriter<byte> .
IBufferWriter<byte> makes it possible to get access to buffers to perform writes without additional buffer copies.
async Task WriteHelloAsync(PipeWriter writer, CancellationToken cancellationToken = default)
{
// Request at least 5 bytes from the PipeWriter.
Memory<byte> memory = writer.GetMemory(5);
await writer.FlushAsync(cancellationToken);
}
Cancellation
FlushAsync supports passing a CancellationToken. Passing a CancellationToken results in an
OperationCanceledException if the token is canceled while there's a flush pending. PipeWriter.FlushAsync supports
a way to cancel the current flush operation via PipeWriter.CancelPendingFlush without raising an exception.
Calling PipeWriter.CancelPendingFlush causes the current or next call to PipeWriter.FlushAsync or
PipeWriter.WriteAsync to return a FlushResult with IsCanceled set to true . This can be useful for halting the
yielding flush in a non-destructive and non-exceptional way.
PipeWriter common problems
GetSpan and GetMemory return a buffer with at least the requested amount of memory. Don't assume exact
buffer sizes.
There's no guarantee that successive calls will return the same buffer or the same-sized buffer.
A new buffer must be requested after calling Advance to continue writing more data. The previously acquired
buffer can't be written to.
Calling GetMemory or GetSpan while there's an incomplete call to FlushAsync isn't safe.
Calling Complete or CompleteAsync while there's unflushed data can result in memory corruption.
IDuplexPipe
The IDuplexPipe is a contract for types that support both reading and writing. For example, a network connection
would be represented by an IDuplexPipe .
Unlike Pipe which contains a PipeReader and a PipeWriter , IDuplexPipe represents a single side of a full duplex
connection. That means what is written to the PipeWriter will not be read from the PipeReader .
Streams
When reading or writing stream data, you typically read data using a de-serializer and write data using a serializer.
Most of these read and write stream APIs have a Stream parameter. To make it easier to integrate with these
existing APIs, PipeReader and PipeWriter expose an AsStream. AsStream returns a Stream implementation
around the PipeReader or PipeWriter .
Work with Buffers in .NET
9 minutes to read • Edit Online
This article provides an overview of types that help read data that runs across multiple buffers. They're primarily
used to support PipeReader objects.
IBufferWriter<T>
System.Buffers.IBufferWriter<T> is a contract for synchronous buffered writing. At the lowest level, the interface:
Is basic and not difficult to use.
Allows access to a Memory<T> or Span<T>. The Memory<T> or Span<T> can be written to and you can
determine how many T items were written.
Because of this mixed representation, the ReadOnlySequence<T> exposes indexes as SequencePosition instead of an
integer. A SequencePosition :
Is an opaque value that represents an index into the ReadOnlySequence<T> where it originated.
Consists of two parts, an integer and an object. What these two values represent are tied to the implementation
of ReadOnlySequence<T> .
Access data
The ReadOnlySequence<T> exposes data as an enumerable of ReadOnlyMemory<T> . Enumerating each of the segments
can be done using a basic foreach:
position += span.Length;
}
return -1;
}
The preceding method searches each segment for a specific byte. If you need to keep track of each segment's
SequencePosition , ReadOnlySequence<T>.TryGet is more appropriate. The next sample changes the preceding
code to return a SequencePosition instead of an integer. Returning a SequencePosition has the benefit of allowing
the caller to avoid a second scan to get the data at a specific index.
The combination of SequencePosition and TryGet acts like an enumerator. The position field is modified at the
start of each iteration to be start of each segment within the ReadOnlySequence<T> .
The preceding method exists as an extension method on ReadOnlySequence<T> . PositionOf can be used to simplify
the preceding code:
Process a ReadOnlySequence<T>
Processing a ReadOnlySequence<T> can be challenging since data may be split across multiple segments within the
sequence. For the best performance, split code into two paths:
A fast path that deals with the single segment case.
A slow path that deals with the data split across segments.
There are a few approaches that can be used to process data in multi-segmented sequences:
Use the SequenceReader<T> .
Parse data segment by segment, keeping track of the SequencePosition and index within the segment parsed.
This avoids unnecessary allocations but may be inefficient, especially for small buffers.
Copy the ReadOnlySequence<T> to a contiguous array and treat it like a single buffer:
If the size of the ReadOnlySequence<T> is small, it may be reasonable to copy the data into a stack-
allocated buffer using the stackalloc operator.
Copy the ReadOnlySequence<T> into a pooled array using ArrayPool<T>.Shared.
Use ReadOnlySequence<T>.ToArray() . This isn't recommended in hot paths as it allocates a new T[] on
the heap.
The following examples demonstrate some common cases for processing ReadOnlySequence<byte> :
P r o c e ss b i n a r y d a t a
The following example parses a 4-byte big-endian integer length from the start of the ReadOnlySequence<byte> .
return true;
}
P r o c e ss t e x t d a t a
if (index != -1)
{
// Check next segment for \n.
if (index + 1 >= span.Length)
{
var next = position;
if (!buffer.TryGet(ref next, out ReadOnlyMemory<byte> nextSegment))
{
// You're at the end of the sequence.
return false;
}
else if (nextSegment.Span[0] == (byte)'\n')
{
// A match was found.
break;
}
}
// Check the current segment of \n.
else if (span[index + 1] == (byte)'\n')
{
// It was found.
break;
}
}
previous = position;
}
if (index != -1)
{
// Get the position just before the \r\n.
var delimeter = buffer.GetPosition(index, previous);
// Slice the buffer to get the remaining data after the line.
buffer = buffer.Slice(buffer.GetPosition(2, delimeter));
return true;
}
return false;
}
Em p t y se g m e n t s
It's valid to store empty segments inside of a ReadOnlySequence<T> . Empty segments may occur while enumerating
segments explicitly:
static void EmptySegments()
{
// This logic creates a ReadOnlySequence<byte> with 4 segments,
// two of which are empty.
var first = new BufferSegment(new byte[0]);
var last = first.Append(new byte[] { 97 })
.Append(new byte[0]).Append(new byte[] { 98 });
Console.WriteLine($"sequence1.Length={sequence1.Length}"); // sequence1.Length=2
Console.WriteLine($"sequence2.Length={sequence2.Length}"); // sequence2.Length=2
// sequence1.FirstSpan.Length=1
Console.WriteLine($"sequence1.FirstSpan.Length={sequence1.FirstSpan.Length}");
The preceding code creates a ReadOnlySequence<byte> with empty segments and shows how those empty segments
affect the various APIs:
ReadOnlySequence<T>.Slice with a SequencePosition pointing to an empty segment preserves that segment.
ReadOnlySequence<T>.Slice with an int skips over the empty segments.
Enumerating the ReadOnlySequence<T> enumerates the empty segments.
Potential problems with ReadOnlySequence<T> and SequencePosition
There are several unusual outcomes when dealing with a ReadOnlySequence<T> / SequencePosition vs. a normal
ReadOnlySpan<T> / ReadOnlyMemory<T> / T[] / int :
SequencePosition is a position marker for a specific ReadOnlySequence<T> , not an absolute position. Because it's
relative to a specific ReadOnlySequence<T> , it doesn't have meaning if used outside of the ReadOnlySequence<T>
where it originated.
Arithmetic can't be performed on SequencePosition without the ReadOnlySequence<T> . That means doing basic
things like position++ is written ReadOnlySequence<T>.GetPosition(position, 1) .
GetPosition(long) does not support negative indexes. That means it's impossible to get the second to last
character without walking all segments.
Two SequencePosition can't be compared, making it difficult to:
Know if one position is greater than or less than another position.
Write some parsing algorithms.
ReadOnlySequence<T> is bigger than an object reference and should be passed by in or ref where possible.
Passing ReadOnlySequence<T> by in or ref reduces copies of the struct.
Empty segments:
Are valid within a ReadOnlySequence<T> .
Can appear when iterating using the ReadOnlySequence<T>.TryGet method.
Can appear slicing the sequence using the ReadOnlySequence<T>.Slice() method with SequencePosition
objects.
SequenceReader<T>
SequenceReader<T>:
Is a new type that was introduced in .NET Core 3.0 to simplify the processing of a ReadOnlySequence<T> .
Unifies the differences between a single segment ReadOnlySequence<T> and multi-segment ReadOnlySequence<T>
.
Provides helpers for reading binary and text data ( byte and char ) that may or may not be split across
segments.
There are built-in methods for dealing with processing both binary and delimited data. The following section
demonstrates what those same methods look like with the SequenceReader<T> :
Access data
SequenceReader<T> has methods for enumerating data inside of the ReadOnlySequence<T> directly. The following
code is an example of processing a ReadOnlySequence<byte> a byte at a time:
The CurrentSpan exposes the current segment's Span , which is similar to what was done in the method manually.
Use position
The following code is an example implementation of FindIndexOf using the SequenceReader<T> :
SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
var reader = new SequenceReader<byte>(buffer);
while (!reader.End)
{
// Search for the byte in the current span.
var index = reader.CurrentSpan.IndexOf(data);
if (index != -1)
{
// It was found, so advance to the position.
reader.Advance(index);
return reader.Position;
}
// Skip the current segment since there's nothing in it.
reader.Advance(reader.CurrentSpan.Length);
}
return null;
}
return true;
}
line = default;
return false;
}
A memory-mapped file contains the contents of a file in virtual memory. This mapping between a file and memory
space enables an application, including multiple processes, to modify the file by reading and writing directly to the
memory. Starting with the .NET Framework 4, you can use managed code to access memory-mapped files in the
same way that native Windows functions access memory-mapped files, as described in Managing Memory-
Mapped Files.
There are two types of memory-mapped files:
Persisted memory-mapped files
Persisted files are memory-mapped files that are associated with a source file on a disk. When the last
process has finished working with the file, the data is saved to the source file on the disk. These memory-
mapped files are suitable for working with extremely large source files.
Non-persisted memory-mapped files
Non-persisted files are memory-mapped files that are not associated with a file on a disk. When the last
process has finished working with the file, the data is lost and the file is reclaimed by garbage collection.
These files are suitable for creating shared memory for inter-process communications (IPC ).
MemoryMappedFile.CreateOrOpen method.
MemoryMappedViewAccessor.SafeMemoryMappedViewHandl
e property.
- or -
MemoryMappedViewStream.SafeMemoryMappedViewHandle
property.
TASK METHODS OR PROPERTIES TO USE
To delay allocating memory until a view is created (non- CreateNew method with the
persisted files only). MemoryMappedFileOptions.DelayAllocatePages value.
Security
You can apply access rights when you create a memory-mapped file, by using the following methods that take a
MemoryMappedFileAccess enumeration as a parameter:
MemoryMappedFile.CreateFromFile
MemoryMappedFile.CreateNew
MemoryMappedFile.CreateOrOpen
You can specify access rights for opening an existing memory-mapped file by using the OpenExisting methods that
take an MemoryMappedFileRights as a parameter.
In addition, you can include a MemoryMappedFileSecurity object that contains predefined access rules.
To apply new or changed access rules to a memory-mapped file, use the SetAccessControl method. To retrieve
access or audit rules from an existing file, use the GetAccessControl method.
Examples
Persisted Memory-Mapped Files
The CreateFromFile methods create a memory-mapped file from an existing file on disk.
The following example creates a memory-mapped view of a part of an extremely large file and manipulates a
portion of it.
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
class Program
{
static void Main(string[] args)
{
long offset = 0x10000000; // 256 megabytes
long length = 0x20000000; // 512 megabytes
Class Program
Sub Main()
Dim offset As Long = &H10000000 ' 256 megabytes
Dim length As Long = &H20000000 ' 512 megabytes
The following example opens the same memory-mapped file for another process.
using System;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
class Program
{
static void Main(string[] args)
{
// Assumes another process has created the memory-mapped file.
using (var mmf = MemoryMappedFile.OpenExisting("ImgA"))
{
using (var accessor = mmf.CreateViewAccessor(4000000, 2000000))
{
int colorSize = Marshal.SizeOf(typeof(MyColor));
MyColor color;
Class Program
Public Shared Sub Main(ByVal args As String())
' Assumes another process has created the memory-mapped file.
Using mmf = MemoryMappedFile.OpenExisting("ImgA")
Using accessor = mmf.CreateViewAccessor(4000000, 2000000)
Dim colorSize As Integer = Marshal.SizeOf(GetType(MyColor))
Dim color As MyColor
Process A
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
class Program
{
// Process A:
static void Main(string[] args)
{
using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("testmap", 10000))
{
bool mutexCreated;
Mutex mutex = new Mutex(true, "testmapmutex", out mutexCreated);
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(1);
}
mutex.ReleaseMutex();
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
BinaryReader reader = new BinaryReader(stream);
Console.WriteLine("Process A says: {0}", reader.ReadBoolean());
Console.WriteLine("Process B says: {0}", reader.ReadBoolean());
Console.WriteLine("Process C says: {0}", reader.ReadBoolean());
}
mutex.ReleaseMutex();
}
}
}
Imports System.IO
Imports System.IO.MemoryMappedFiles
Imports System.Threading
Module Module1
' Process A:
Sub Main()
Using mmf As MemoryMappedFile = MemoryMappedFile.CreateNew("testmap", 10000)
Dim mutexCreated As Boolean
Dim mTex As Mutex = New Mutex(True, "testmapmutex", mutexCreated)
Using Stream As MemoryMappedViewStream = mmf.CreateViewStream()
Dim writer As BinaryWriter = New BinaryWriter(Stream)
writer.Write(1)
End Using
mTex.ReleaseMutex()
Console.WriteLine("Start Process B and press ENTER to continue.")
Console.ReadLine()
mTex.WaitOne()
Using Stream As MemoryMappedViewStream = mmf.CreateViewStream()
Dim reader As BinaryReader = New BinaryReader(Stream)
Console.WriteLine("Process A says: {0}", reader.ReadBoolean())
Console.WriteLine("Process B says: {0}", reader.ReadBoolean())
Console.WriteLine("Process C says: {0}", reader.ReadBoolean())
End Using
mTex.ReleaseMutex()
End Using
End Sub
End Module
Process B
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
class Program
{
// Process B:
static void Main(string[] args)
{
try
{
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
{
Imports System.IO
Imports System.IO.MemoryMappedFiles
Imports System.Threading
Module Module1
' Process B:
Sub Main()
Try
Using mmf As MemoryMappedFile = MemoryMappedFile.OpenExisting("testmap")
Dim mTex As Mutex = Mutex.OpenExisting("testmapmutex")
mTex.WaitOne()
Using Stream As MemoryMappedViewStream = mmf.CreateViewStream(1, 0)
Dim writer As BinaryWriter = New BinaryWriter(Stream)
writer.Write(0)
End Using
mTex.ReleaseMutex()
End Using
Catch noFile As FileNotFoundException
Console.WriteLine("Memory-mapped file does not exist. Run Process A first." & vbCrLf &
noFile.Message)
End Try
End Sub
End Module
Process C
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
class Program
{
// Process C:
static void Main(string[] args)
{
try
{
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
{
Imports System.IO
Imports System.IO.MemoryMappedFiles
Imports System.Threading
Module Module1
' Process C:
Sub Main()
Try
Using mmf As MemoryMappedFile = MemoryMappedFile.OpenExisting("testmap")
Dim mTex As Mutex = Mutex.OpenExisting("testmapmutex")
mTex.WaitOne()
Using Stream As MemoryMappedViewStream = mmf.CreateViewStream(2, 0)
Dim writer As BinaryWriter = New BinaryWriter(Stream)
writer.Write(1)
End Using
mTex.ReleaseMutex()
End Using
Catch noFile As FileNotFoundException
Console.WriteLine("Memory-mapped file does not exist. Run Process A first, then B." & vbCrLf &
noFile.Message)
End Try
End Sub
End Module
See also
File and Stream I/O