Code Listings

Chapter 14: Streams and I/O

Basic use of a stream:

using System;
using System.IO;

class Program
{
  static void Main()
  {
    // Create a file called test.txt in the current directory:
    using (Stream s = new FileStream ("test.txt", FileMode.Create))
    {
      Console.WriteLine (s.CanRead);       // true
      Console.WriteLine (s.CanWrite);      // true
      Console.WriteLine (s.CanSeek);       // true

      s.WriteByte (101);
      s.WriteByte (102);
      byte[] block = { 1, 2, 3, 4, 5 }; 
      s.Write (block, 0, block.Length);     // Write block of 5 bytes

      Console.WriteLine (s.Length);         // 7
      Console.WriteLine (s.Position);       // 7
      s.Position = 0;                       // Move back to the start

      Console.WriteLine (s.ReadByte());     // 101
      Console.WriteLine (s.ReadByte());     // 102

      // Read from the stream back into the block array:
      Console.WriteLine (s.Read (block, 0, block.Length));   // 5

      // Assuming the last Read returned 5, we'll be at
      // the end of the file, so Read will now return 0:
      Console.WriteLine (s.Read (block, 0, block.Length));   // 0
    }
  }
}

Reading a 1000 bytes from a stream:

byte[] data = new byte [1000];

// bytesRead will always end up at 1000, unless the stream is
// itself smaller in length:

int bytesRead = 0;
int chunkSize = 1;
while (bytesRead < data.Length && chunkSize > 0)
  bytesRead +=
    chunkSize = s.Read (data, bytesRead, data.Length - bytesRead);

An easier way:

byte[] data = new BinaryReader (s).ReadBytes (1000);

Reading into a MemoryStream:

static MemoryStream ToMemoryStream (Stream input, bool closeInput)
{
  try
  {                                         // Read and write in
    byte[] block = new byte [0x1000];       // blocks of 4K.
    MemoryStream ms = new MemoryStream();
    while (true)
    {
      int bytesRead = input.Read (block, 0, block.Length);
      if (bytesRead == 0) return ms;
      ms.Write (block, 0, bytesRead);
    }
  }
  finally { if (closeInput) input.Close(); }
}

Named pipes:

// Server:
using (var s = new NamedPipeServerStream ("pipedream")
{
  s.WaitForConnection();
  s.WriteByte (100);
  Console.WriteLine (s.ReadByte());
}
// Client:
using (var s = new NamedPipeClientStream ("pipedream"))
{
  s.Connect();
  Console.WriteLine (s.ReadByte());
  s.WriteByte (200);                 // Send the value 200 back.
}

Message transmission mode:

static byte[] ReadMessage (PipeStream s)
{
  MemoryStream ms = new MemoryStream();
  byte[] buffer = new byte [0x1000];      // Read in 4KB blocks

  do    { ms.Write (buffer, 0, s.Read (buffer, 0, buffer.Length)); }
  while (!s.IsMessageComplete);

  return ms.ToArray();
}
// Server:
using (var s = new NamedPipeServerStream ("pipedream", PipeDirection.InOut,
                                          1, PipeTransmissionMode.Message))
{
  s.WaitForConnection();
  
  byte[] msg = Encoding.UTF8.GetBytes ("Hello");
  s.Write (msg, 0, msg.Length);
  
  Console.WriteLine (Encoding.UTF8.GetString (ReadMessage (s)));
}
// Client:
using (var s = new NamedPipeClientStream ("pipedream"))
{
  s.Connect();
  s.ReadMode = PipeTransmissionMode.Message;

  Console.WriteLine (Encoding.UTF8.GetString (ReadMessage (s)));

  byte[] msg = Encoding.UTF8.GetBytes ("Hello right back!");
  s.Write (msg, 0, msg.Length);
}

Anonymous pipes server:

string clientExe = "d:\PipeDemo\ClientDemo.exe";

HandleInheritability inherit = HandleInheritability.Inheritable;

using (var tx = new AnonymousPipeServerStream (PipeDirection.Out, inherit))
using (var rx = new AnonymousPipeServerStream (PipeDirection.In, inherit))
{
  string txID = tx.GetClientHandleAsString();
  string rxID = rx.GetClientHandleAsString();

  var startInfo = new ProcessStartInfo (clientExe, txID + " " + rxID);
  startInfo.UseShellExecute = false;    // Required for child process
  Process p = Process.Start (startInfo);

  tx.DisposeLocalCopyOfClientHandle();    // Release unmanaged
  rx.DisposeLocalCopyOfClientHandle();    // handle resources.

  tx.WriteByte (100);
  Console.WriteLine ("Server received: " + rx.ReadByte());

  p.WaitForExit();
}

Anonymous pipes client:

static void Main (string[] args)
{
  string rxID = args[0];    // Note we’re reversing the
  string txID = args[1];    // receive and transmit roles.

  using (var rx = new AnonymousPipeClientStream (PipeDirection.In, rxID))
  using (var tx = new AnonymousPipeClientStream (PipeDirection.Out, txID))
  {
    Console.WriteLine ("Client received: " + rx.ReadByte());
    tx.WriteByte (200);
  }
}

BufferedStream:

// Write 100K to a file:
File.WriteAllBytes ("myFile.bin", new byte [100000]);

using (FileStream fs = File.OpenRead ("myFile.bin"))
using (BufferedStream bs = new BufferedStream (fs, 20000))  //20K buffer
{
  bs.ReadByte();
  Console.WriteLine (fs.Position);         // 20000
}

StreamReader and StreamWriter:

using (FileStream fs = File.Create ("test.txt"))
using (TextWriter writer = new StreamWriter (fs))
{
  writer.WriteLine ("Line1");
  writer.WriteLine ("Line2");
}

using (FileStream fs = File.OpenRead ("test.txt"))
using (TextReader reader = new StreamReader (fs))
{
  Console.WriteLine (reader.ReadLine());       // Line1
  Console.WriteLine (reader.ReadLine());       // Line2
}
using (TextWriter writer = File.CreateText ("test.txt"))
{
  writer.WriteLine ("Line1");
  writer.WriteLine ("Line2");
}
using (TextWriter writer = File.AppendText ("test.txt"))
  writer.WriteLine ("Line3");

using (TextReader reader = File.OpenText ("test.txt"))
  while (reader.Peek() > -1)
    Console.WriteLine (reader.ReadLine());     // Line1
                                               // Line2
                                               // Line3
using (TextWriter w = File.CreateText ("data.txt"))
{
  w.WriteLine (123);          // Writes "123"
  w.WriteLine (true);         // Writes the word "true"
}
using (TextReader r = File.OpenText ("data.txt"))
{
  int myInt = int.Parse (r.ReadLine());     // myInt == 123
  bool yes = bool.Parse (r.ReadLine());     // yes == true
}

Character encodings:

using (TextWriter w = File.CreateText ("but.txt"))    // Use default UTF-8
  w.WriteLine ("but—");                               // encoding.

using (Stream s = File.OpenRead ("but.txt"))
  for (int b; (b = s.ReadByte()) > -1;)
    Console.WriteLine (b);
using (Stream s = File.Create ("but.txt"))
using (TextWriter w = new StreamWriter (s, Encoding.Unicode))
  w.WriteLine ("but—");

foreach (byte b in File.ReadAllBytes ("but.txt"))
  Console.WriteLine (b);

Binary adapaters:

public class Person
{
  public string Name;
  public int    Age;
  public double Height;

  public void SaveData (Stream s)
  {
    BinaryWriter w = new BinaryWriter (s);
    w.Write (Name);
    w.Write (Age);
    w.Write (Height);
    w.Flush();         // Ensure the BinaryWriter buffer is cleared.
                       // We won't dispose/close it, so more data
  }                    // can to be written to the stream.

  public void LoadData (Stream s)
  {
    BinaryReader r = new BinaryReader (s);
    Name   = r.ReadString();
    Age    = r.ReadInt32();
    Height = r.ReadDouble();
  }
}

Compressing a directory:

static uint CompressFolder (string folder, bool recursive)
{
  string path = "Win32_Directory.Name='" + folder + "'";
  using (ManagementObject dir = new ManagementObject (path))
  using (ManagementBaseObject p = dir.GetMethodParameters ("CompressEx"))
  {
    p ["Recursive"] = recursive;
    using (ManagementBaseObject result = dir.InvokeMethod ("CompressEx",
                                                             p, null))
      return (uint) result.Properties ["ReturnValue"].Value;
  }
}

Determining if a volume supports compression & encryption:

using System;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;

class SupportsCompressionEncryption
{
  const int SupportsCompression = 0x10;
  const int SupportsEncryption = 0x20000;

  [DllImport ("Kernel32.dll", SetLastError = true)]
  extern static bool GetVolumeInformation (string vol, StringBuilder name,
    int nameSize, out uint serialNum, out uint maxNameLen, out uint flags,
    StringBuilder fileSysName, int fileSysNameSize);

  static void Main()
  {
    uint serialNum, maxNameLen, flags;
    bool ok = GetVolumeInformation (@"C:\", null, 0, out serialNum,
                                    out maxNameLen, out flags, null, 0);
    if (!ok)
      throw new Exception
        ("Error: Win32 code=" + Marshal.GetLastWin32Error());

    bool canCompress = (flags & SupportsCompression) > 0;
    bool canEncrypt = (flags & SupportsEncryption) > 0;
  }
}

File security:

using System;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;

public static void Main()
{
  FileSecurity sec = File.GetAccessControl (@"c:\temp\test.txt");
  AuthorizationRuleCollection rules = sec.GetAccessRules (true, true,
                                                       typeof (NTAccount));
  foreach (FileSystemAccessRule rule in rules)
  {
    Console.WriteLine (rule.AccessControlType);         // Allow or Deny
    Console.WriteLine (rule.FileSystemRights);          // e.g. FullControl
    Console.WriteLine (rule.IdentityReference.Value);   // e.g. MyDomain/Joe
  }

  FileSystemAccessRule newRule = new FileSystemAccessRule
    ("Users", FileSystemRights.ExecuteFile, AccessControlType.Allow);
  sec.AddAccessRule (newRule);
  File.SetAccessControl (@"c:\temp\test.txt", sec);
}

FileInfo and DirectoryInfo:

FileInfo fi = new FileInfo (@"c:\temp\FileInfo.txt");
Console.WriteLine (fi.Exists);         // false

using (TextWriter w = fi.CreateText())
  w.Write ("Some text");

Console.WriteLine (fi.Exists);         // false (still)
fi.Refresh();
Console.WriteLine (fi.Exists);         // true

Console.WriteLine (fi.Name);           // FileInfo.txt
Console.WriteLine (fi.FullName);       // c:\temp\FileInfo.txt
Console.WriteLine (fi.DirectoryName);  // c:\temp
Console.WriteLine (fi.Directory.Name); // temp
Console.WriteLine (fi.Extension);      // .txt
Console.WriteLine (fi.Length);         // 9

fi.Encrypt();
fi.Attributes ^= FileAttributes.Hidden;   // (Toggle hidden flag)
fi.IsReadOnly = true;

Console.WriteLine (fi.Attributes);    // ReadOnly,Archive,Hidden,Encrypted
Console.WriteLine (fi.CreationTime);

fi.MoveTo (@"c:\temp\FileInfoX.txt");

DirectoryInfo di = fi.Directory;
Console.WriteLine (di.Name);             // temp
Console.WriteLine (di.FullName);         // c:\temp
Console.WriteLine (di.Parent.FullName);  // c:\
di.CreateSubdirectory ("SubFolder");

Enumerating files and subdirectories:

DirectoryInfo di = new DirectoryInfo (@"e:\photos");

foreach (FileInfo fi in di.GetFiles ("*.jpg"))
  Console.WriteLine (fi.Name);

foreach (DirectoryInfo subDir in di.GetDirectories())
  Console.WriteLine (subDir.FullName);

Querying volume information:

DriveInfo c = new DriveInfo ("C");       // Query the C: drive.

long totalSize = c.TotalSize;            // Size in bytes.
long freeBytes = c.TotalFreeSpace;       // Ignores disk quotas.
long freeToMe  = c.AvailableFreeSpace;   // Takes quotas into account.

foreach (DriveInfo d in DriveInfo.GetDrives())    // All defined drives.
{
  Console.WriteLine (d.Name);             // C:\
  Console.WriteLine (d.DriveType);        // Fixed
  Console.WriteLine (d.RootDirectory);    // C:\

  if (d.IsReady)   // If the drive is not ready, the following two
                   // properties will throw exceptions:
  {
    Console.WriteLine (d.VolumeLabel);    // The Sea Drive
    Console.WriteLine (d.DriveFormat);    // NTFS
  }
}

Catching filesystem events:

static void Main() { Watch (@"c:\temp", "*.txt", true); }

static void Watch (string path, string filter, bool includeSubDirs)
{
  using (FileSystemWatcher watcher = new FileSystemWatcher (path, filter))
  {
    watcher.Created += FileCreatedChangedDeleted;
    watcher.Changed += FileCreatedChangedDeleted;
    watcher.Deleted += FileCreatedChangedDeleted;
    watcher.Renamed += FileRenamed;
    watcher.Error   += FileError;

    watcher.IncludeSubdirectories = includeSubDirs;
    watcher.EnableRaisingEvents = true;

    Console.WriteLine ("Listening for events - press <enter> to end");
    Console.ReadLine();
  }
  // Disposing the FileSystemWatcher stops further events from firing.
}

static void FileCreatedChangedDeleted (object o, FileSystemEventArgs e)
{
  Console.WriteLine ("File {0} has been {1}", e.FullPath, e.ChangeType);
}

static void FileRenamed (object o, RenamedEventArgs e)
{
  Console.WriteLine ("Renamed: {0}->{1}", e.OldFullPath, e.FullPath);
}

static void FileError (object o, ErrorEventArgs e)
{
  Console.WriteLine ("Error: " + e.GetException().Message);
}

Memory-mapped files

File.WriteAllBytes ("long.bin", new byte [1000000]);

using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile ("long.bin"))
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
{
  accessor.Write (500000, (byte) 77);
  Console.WriteLine (accessor.ReadByte (500000));   // 77
}

Memory-mapped files and shared memory

using (MemoryMappedFile mmFile = MemoryMappedFile.CreateNew ("Demo", 500))
using (MemoryMappedViewAccessor accessor = mmFile.CreateViewAccessor())
{
  accessor.Write (0, 12345);
  Console.ReadLine();   // Keep shared memory alive until user hits Enter.
}
// This can run in a separate EXE:
using (MemoryMappedFile mmFile = MemoryMappedFile.OpenExisting ("Demo"))
using (MemoryMappedViewAccessor accessor = mmFile.CreateViewAccessor())
  Console.WriteLine (accessor.ReadInt32 (0));   // 12345

Working with View Accessors

byte[] data = Encoding.UTF8.GetBytes ("This is a test");
accessor.Write (0, data.Length);
accessor.WriteArray (4, data, 0, data.Length);
byte[] data = new byte [accessor.ReadInt32 (0)];
accessor.ReadArray (4, data, 0, data.Length);
Console.WriteLine (Encoding.UTF8.GetString (data));   // This is a test
struct Data { public int X, Y; }
...
var data = new Data { X = 123, Y = 456 };
accessor.Write (0, ref data);
accessor.Read (0, out data);
Console.WriteLine (data.X + " " + data.Y);   // 123 456
unsafe
{
  byte* pointer = null;
  accessor.SafeMemoryMappedViewHandle.AcquirePointer (ref pointer);
  int* intPointer = (int*) pointer;
  Console.WriteLine (*intPointer);               // 123
}

Compression:

using (Stream s = File.Create ("compressed.bin"))
using (Stream ds = new DeflateStream (s, CompressionMode.Compress))
  for (byte i = 0; i < 100; i++)
    ds.WriteByte (i);

using (Stream s = File.OpenRead ("compressed.bin"))
using (Stream ds = new DeflateStream (s, CompressionMode.Decompress))
  for (byte i = 0; i < 100; i++)
    Console.WriteLine (ds.ReadByte());     // Writes 0 to 99 
string[] words = "The quick brown fox jumps over the lazy dog".Split();
Random rand = new Random();

using (Stream s = File.Create ("compressed.bin"))
using (Stream ds = new DeflateStream (s, CompressionMode.Compress))
using (TextWriter w = new StreamWriter (ds))
  for (int i = 0; i < 1000; i++)
    w.Write (words [rand.Next (words.Length)] + " ");

Console.WriteLine (new FileInfo ("compressed.bin").Length);      // 1073

using (Stream s = File.OpenRead ("compressed.bin"))
using (Stream ds = new DeflateStream (s, CompressionMode.Decompress))
using (TextReader r = new StreamReader (ds))
  Console.Write (r.ReadToEnd());                 // Output below:

Compressing in memory, option 1:

byte[] data = new byte[1000];          // We can expect a good compression
                                       // ratio from an empty array!
MemoryStream ms = new MemoryStream();
using (Stream ds = new DeflateStream (ms, CompressionMode.Compress))
  ds.Write (data, 0, data.Length);

byte[] compressed = ms.ToArray();
Console.WriteLine (compressed.Length);       // 113

// Decompress back to the data array:
ms = new MemoryStream (compressed);
using (Stream ds = new DeflateStream (ms, CompressionMode.Decompress))
  for (int i = 0; i < 1000; i += ds.Read (data, i, 1000 - i));

Compressing in memory, option 2:

byte[] data = new byte[1000];

MemoryStream ms = new MemoryStream();
using (Stream ds = new DeflateStream (ms, CompressionMode.Compress, true))
  ds.Write (data, 0, data.Length);

Console.WriteLine (ms.Length);             // 113
ms.Position = 0;
using (Stream ds = new DeflateStream (ms, CompressionMode.Decompress))
  for (int i = 0; i < 1000; i += ds.Read (data, i, 1000 - i));

Reading and writing isolated storage:

// IsolatedStorage classes live in System.IO.IsolatedStorage

using (IsolatedStorageFile f =
       IsolatedStorageFile.GetMachineStoreForDomain())
using (var s = new IsolatedStorageFileStream ("hi.txt",FileMode.Create,f))
using (var writer = new StreamWriter (s))
  writer.WriteLine ("Hello, World");

// Read it back:

using (IsolatedStorageFile f =
       IsolatedStorageFile.GetMachineStoreForDomain())
using (var s = new IsolatedStorageFileStream ("hi.txt", FileMode.Open, f))
using (var reader = new StreamReader (s))
  Console.WriteLine (reader.ReadToEnd());        // Hello, world

Roaming user isolation:

var flags = IsolatedStorageScope.Assembly
          | IsolatedStorageScope.User
          | IsolatedStorageScope.Roaming;

using (IsolatedStorageFile f = IsolatedStorageFile.GetStore (flags,
                                                             null, null))
using (var s = new IsolatedStorageFileStream ("a.txt", FileMode.Create, f))
using (var writer = new StreamWriter (s))
  writer.WriteLine ("Hello, World");

Enumerating isolated storage:

using (IsolatedStorageFile f = IsolatedStorageFile.GetUserStoreForDomain())
{
  using (var s = new IsolatedStorageFileStream ("f1.x",FileMode.Create,f))
    s.WriteByte (123);

  using (var s = new IsolatedStorageFileStream ("f2.x",FileMode.Create,f))
    s.WriteByte (123);

  foreach (string s in f.GetFileNames ("*.*"))
    Console.Write (s + " ");                   // f1.x f2.x
}

Creating and removing subdirectories:

using (IsolatedStorageFile f = IsolatedStorageFile.GetUserStoreForDomain())
{
  f.CreateDirectory ("subfolder");

  foreach (string s in f.GetDirectoryNames ("*.*"))
    Console.WriteLine (s);                             // subfolder

  using (var s = new IsolatedStorageFileStream (@"subfolder\sub1.txt",
                                                 FileMode.Create, f))
    s.WriteByte (100);

  f.DeleteFile (@"subfolder\sub1.txt");
  f.DeleteDirectory ("subfolder");
}

Enumerating over other applications' stores:

System.Collections.IEnumerator rator =
  IsolatedStorageFile.GetEnumerator (IsolatedStorageScope.User);

while (rator.MoveNext())
{
  var isf = (IsolatedStorageFile) rator.Current;

  Console.WriteLine (isf.AssemblyIdentity);      // Strong name or URI
  Console.WriteLine (isf.CurrentSize);
  Console.WriteLine (isf.Scope);                 // User + ...
}
C# 12 in a Nutshell
Buy from amazon.com Buy print or Kindle edition
Buy from ebooks.com Buy PDF edition
Buy from O'Reilly Read via O'Reilly subscription