Chapter 15 - Streams and IO
Create and Write
// 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 }
Async Demo
using (Stream s = new FileStream ("test.txt", FileMode.Create)) { byte[] block = { 1, 2, 3, 4, 5 }; await s.WriteAsync (block, 0, block.Length); // Write asychronously s.Position = 0; // Move back to the start // Read from the stream back into the block array: Console.WriteLine (await s.ReadAsync (block, 0, block.Length)); // 5 } // Clean up File.Delete ("test.txt");
Named Pipe Server
bool messageMode = true; // Be sure Server and Client set the same if (messageMode) { 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))); } else { using var s = new NamedPipeServerStream ("pipedream"); Console.WriteLine ("Please start Named Pipe Client."); s.WaitForConnection(); s.WriteByte (100); // Send the value 100. Console.WriteLine ("Response from Named Pipe Client."); Console.WriteLine (s.ReadByte()); } static byte[] ReadMessage (PipeStream s) { MemoryStream ms = new MemoryStream(); byte[] buffer = new byte [0x1000]; // Read in 4 KB blocks do { ms.Write (buffer, 0, s.Read (buffer, 0, buffer.Length)); } while (!s.IsMessageComplete); return ms.ToArray(); }
Named Pipe Client
bool messageMode = true; // Be sure Server and Client set the same if (messageMode) { 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); } else { using var s = new NamedPipeClientStream ("pipedream"); s.Connect(); Console.WriteLine (s.ReadByte()); s.WriteByte (200); // Send the value 200 back. } static byte[] ReadMessage (PipeStream s) { MemoryStream ms = new MemoryStream(); byte[] buffer = new byte [0x1000]; // Read in 4 KB blocks do { ms.Write (buffer, 0, s.Read (buffer, 0, buffer.Length)); } while (!s.IsMessageComplete); return ms.ToArray(); }
Stream Reader and Writer
using (FileStream fs = File.Create ("test.txt")) using (TextWriter writer = new StreamWriter (fs)) { var nl = string.Join ("", writer.NewLine.Select (c => $"0x{((int)c).ToString("X2")} ")); Console.WriteLine ($"Newline is {nl} "); 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 }
Encoding
Console.WriteLine ("Default UTF-8 encoding"); using (TextWriter w = File.CreateText ("but.txt")) // Use default UTF-8 encoding. w.WriteLine ("but–"); // Emdash, not the "minus" character using (Stream s = File.OpenRead ("but.txt")) for (int b; (b = s.ReadByte()) > -1;) Console.WriteLine (b); Console.WriteLine("Unicode a.k.a. UTF-16 encoding"); 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);
Compression Streams
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); new FileInfo ("compressed.bin").Length.Dump ("Length of compressed file"); 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
Compressing Text
string[] words = "The quick brown fox jumps over the lazy dog".Split(); Random rand = new Random(0); using (Stream s = File.Create ("compressed.bin")) using (Stream ds = new BrotliStream (s, CompressionMode.Compress)) using (TextWriter w = new StreamWriter (ds)) for (int i = 0; i < 1000; i++) await w.WriteAsync (words [rand.Next (words.Length)] + " "); new FileInfo ("compressed.bin").Length.Dump ("Length of compressed file"); using (Stream s = File.OpenRead ("compressed.bin")) using (Stream ds = new BrotliStream (s, CompressionMode.Decompress)) using (TextReader r = new StreamReader (ds)) Console.Write (await r.ReadToEndAsync()); // Output below:
Compressing in Memory
byte[] data = new byte [1000]; // We can expect a good compression // ratio from an empty array! var 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); // 11 // 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 - alternative
byte[] data = new byte[1000]; MemoryStream ms = new MemoryStream(); using (Stream ds = new DeflateStream (ms, CompressionMode.Compress, true)) await ds.WriteAsync (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 += await ds.ReadAsync (data, i, 1000 - i)) ;
GZip Stream
async Task Main() { string textfile = "myfile.txt"; File.WriteAllText (textfile, RandomString (4096)); var backup = textfile.Replace (".txt", "-Copy.txt"); File.Copy (textfile, backup); await GZip (textfile); await GUnzip (textfile + ".gz", false); // Don't delete it so we can list it for comparison foreach (var fi in Directory .EnumerateFiles (Environment.CurrentDirectory, "myfile*") .Select (f => new FileInfo (f))) Console.WriteLine ($"{fi.Name} {fi.Length} bytes"); File.Delete (textfile); File.Delete (textfile + ".gz"); File.Delete (backup); } async Task GZip (string sourcefile, bool deleteSource = true) { var gzipfile = $"{sourcefile}.gz"; if (File.Exists (gzipfile)) throw new Exception ("Gzip file already exists"); // Compress using (FileStream inStream = File.Open (sourcefile, FileMode.Open)) using (FileStream outStream = new FileStream (gzipfile, FileMode.CreateNew)) using (GZipStream gzipStream = new GZipStream (outStream, CompressionMode.Compress)) await inStream.CopyToAsync (gzipStream); // Or .CopyTo() for non-async code if (deleteSource) File.Delete (sourcefile); } async Task GUnzip (string gzipfile, bool deleteGzip = true) { if (Path.GetExtension (gzipfile) != ".gz") throw new Exception ("Not a gzip file"); var uncompressedFile = gzipfile.Substring (0, gzipfile.Length - 3); if (File.Exists (uncompressedFile)) throw new Exception ("Destination file already exists"); // Uncompress using (FileStream uncompressToStream = File.Open (uncompressedFile, FileMode.Create)) using (FileStream zipfileStream = File.Open (gzipfile, FileMode.Open)) using (var unzipStream = new GZipStream (zipfileStream, CompressionMode.Decompress)) await unzipStream.CopyToAsync (uncompressToStream); // Or .CopyTo() for non-async code if (deleteGzip) File.Delete (gzipfile); } private static Random rnd = new Random(); // http://stackoverflow.com/a/1344242/141172 public static string RandomString (int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; return new string (Enumerable.Repeat (chars, length) .Select (s => s [rnd.Next (s.Length)]).ToArray()); }
Zip
void Main() { AddFilesToFolder(); var zipPath = Path.Combine(DirectoryToZip, "..", "archive.zip"); ZipFile.CreateFromDirectory (DirectoryToZip, zipPath); Directory.CreateDirectory(DirectoryToExtractTo); ZipFile.ExtractToDirectory (zipPath, DirectoryToExtractTo); Console.WriteLine("Extracted files:"); foreach (var file in Directory.EnumerateFiles(DirectoryToExtractTo)) Console.WriteLine(file); } string DirectoryToZip { get { return RuntimeInformation.IsOSPlatform (OSPlatform.Windows) ? @".\MyFolder" : "./MyFolder"; } } string DirectoryToExtractTo { get { return RuntimeInformation.IsOSPlatform (OSPlatform.Windows) ? @".\Extracted" : "./Extracted"; } } void AddFilesToFolder() { Directory.CreateDirectory (DirectoryToZip); foreach (var c in new char[] { 'A', 'B', 'C' }) File.WriteAllText (Path.Combine (DirectoryToZip, $"{c}.txt"), $"This is {c}"); }
File Attributes
string filePath = @"test.txt"; File.WriteAllText(filePath, "The quick brown fox."); FileAttributes fa = File.GetAttributes (filePath); if ((fa & FileAttributes.ReadOnly) != 0) { // Use the exclusive-or operator (^) to toggle the ReadOnly flag fa ^= FileAttributes.ReadOnly; File.SetAttributes (filePath, fa); } Console.WriteLine (new FileInfo ("test.txt").IsReadOnly); new FileInfo ("test.txt").IsReadOnly = true; Console.WriteLine (new FileInfo ("test.txt").IsReadOnly); new FileInfo ("test.txt").IsReadOnly = false; Console.WriteLine (new FileInfo ("test.txt").IsReadOnly); // Now we can delete the file, for instance: File.Delete (filePath);
CompressFolder
void Main() { CreateCompressStructure(); CompressFolder (DirectoryToCompress, true); CleanupCompressStructure(); } static void CreateCompressStructure() { Directory.CreateDirectory (DirectoryToCompress); File.WriteAllText (Path.Combine (DirectoryToCompress, "MyFile.txt"), "C# is fun!"); var subfolder = Path.Combine (DirectoryToCompress, "Subfolder"); Directory.CreateDirectory (subfolder); File.WriteAllText (Path.Combine (subfolder, "FileInSubfolder.txt"), ".NET Core rocks!"); } static void CleanupCompressStructure() { var subfolder = Path.Combine (DirectoryToCompress, "Subfolder"); File.Delete(Path.Combine (subfolder, "FileInSubfolder.txt")); Directory.Delete(subfolder); File.Delete(Path.Combine (DirectoryToCompress, "MyFile.txt")); Directory.Delete(DirectoryToCompress); } 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; } } static string DirectoryToCompress { get { return RuntimeInformation.IsOSPlatform (OSPlatform.Windows) ? @"C:\Temp\CompressMe" : "/tmp/CompressMe"; // Requires a path outside LINQPad's current directory } }
Enumerate File Security
try { File.WriteAllText("sectest.txt", "File for testing security."); FileSecurity fSecurity = new FileSecurity("sectest.txt", AccessControlSections.Owner | AccessControlSections.Group | AccessControlSections.Access); Console.WriteLine (string.Join (Environment.NewLine, fSecurity.GetAccessRules (true, true, typeof (NTAccount)) .Cast<AuthorizationRule>() .Select (r => r.IdentityReference.Value))); } catch (PlatformNotSupportedException ex) { Console.WriteLine ($"Not supported: {ex.Message}"); } finally { File.Delete ("sectest.txt"); }
Modify Access Control
void ShowSecurity (FileSecurity sec) { AuthorizationRuleCollection rules = sec.GetAccessRules (true, true, typeof (NTAccount)); foreach (FileSystemAccessRule r in rules.Cast<FileSystemAccessRule>() .OrderBy (rule => rule.IdentityReference.Value)) { // e.g., MyDomain/Joe Console.WriteLine ($" {r.IdentityReference.Value}"); // Allow or Deny: e.g., FullControl Console.WriteLine ($" {r.FileSystemRights}: {r.AccessControlType}"); } } var file = "sectest.txt"; File.WriteAllText (file, "File security."); var sid = new SecurityIdentifier (WellKnownSidType.BuiltinUsersSid, null); string usersAccount = sid.Translate (typeof (NTAccount)).ToString(); Console.WriteLine ($"User: {usersAccount}"); FileSecurity sec = new FileSecurity (file, AccessControlSections.Owner | AccessControlSections.Group | AccessControlSections.Access); Console.WriteLine ("AFTER CREATE:"); ShowSecurity(sec); sec.ModifyAccessRule (AccessControlModification.Add, new FileSystemAccessRule (usersAccount, FileSystemRights.Write, AccessControlType.Allow), out bool modified); Console.WriteLine ("AFTER MODIFY:"); ShowSecurity (sec);
File Info
void Main() { Directory.CreateDirectory (TempDirectory); FileInfo fi = new FileInfo (Path.Combine(TempDirectory, "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 (Windows) // /tmp/FileInfo.txt (Unix) Console.WriteLine (fi.DirectoryName); // c:\temp (Windows) // /tmp (Unix) 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); // 3/09/2019 1:24:05 PM fi.MoveTo (Path.Combine(TempDirectory, "FileInfoX.txt")); DirectoryInfo di = fi.Directory; Console.WriteLine (di.Name); // temp or tmp Console.WriteLine (di.FullName); // c:\temp or /tmp Console.WriteLine (di.Parent.FullName); // c:\ di.CreateSubdirectory ("SubFolder"); Cleanup(); } static string TempDirectory { get { return RuntimeInformation.IsOSPlatform (OSPlatform.Windows) ? @"C:\Temp" : "/tmp"; } } // Clean up static void Cleanup() { var subfolder = Path.Combine(TempDirectory, "SubFolder"); if (Directory.Exists(subfolder)) Directory.Delete (subfolder); var fi = new FileInfo (Path.Combine(TempDirectory, "FileInfo.txt")); if (fi.Exists) { fi.Attributes &= ~FileAttributes.Hidden; // (Toggle hidden flag) fi.IsReadOnly = false; fi.Delete(); } var fiX = new FileInfo (Path.Combine(TempDirectory, "FileInfoX.txt")); if (fiX.Exists) { fiX.Attributes &= ~FileAttributes.Hidden; // (Toggle hidden flag) fiX.IsReadOnly = false; fiX.Delete(); } }
Special Folders
foreach (var val in Enum.GetValues(typeof(Environment.SpecialFolder)).Cast<Environment.SpecialFolder>().Distinct().OrderBy(v =>v.ToString())) { $"{Environment.GetFolderPath (val)}".Dump(val.ToString()); }
Drive 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().OrderBy(d => d.Name)) // 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 } Console.WriteLine(); }
File Watcher
static void Main() { Watch (TempDirectory, "*.txt", true); } static void Watch (string path, string filter, bool includeSubDirs) { using (var watcher = new FileSystemWatcher (path, filter)) { watcher.Created += FileCreatedChangedDeleted; watcher.Changed += FileCreatedChangedDeleted; watcher.Deleted += FileCreatedChangedDeleted; watcher.Renamed += FileRenamed; watcher.Error += FileError; watcher.IncludeSubdirectories = includeSubDirs; watcher.EnableRaisingEvents = true; Console.WriteLine ("Listening for events - stop query 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); static string TempDirectory { get => RuntimeInformation.IsOSPlatform (OSPlatform.Windows) ? @"C:\Temp" : "/tmp"; }
Get Volume Information
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() { string volume = @"C:\"; uint serialNum, maxNameLen, flags; bool ok = GetVolumeInformation (volume, null, 0, out serialNum, out maxNameLen, out flags, null, 0); if (!ok) throw new Win32Exception(); bool canCompress = (flags & SupportsCompression) != 0; bool canEncrypt = (flags & SupportsEncryption) != 0; Console.WriteLine ($"Volume {volume} supports compression: {canCompress}; encryption: {canEncrypt}"); } }
Memory Mapped
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 File.WriteAllBytes ("short.bin", new byte [1]); using MemoryMappedFile mmfSh = MemoryMappedFile.CreateFromFile ("short.bin", FileMode.Create, null, 1000); using MemoryMappedViewAccessor accessorSh = mmfSh.CreateViewAccessor(); accessorSh.Write (500, (byte)42); Console.WriteLine (accessorSh.ReadByte (500));
Memory Mapped Named Reader
// This can run in a separate executable: using MemoryMappedFile mmFile = MemoryMappedFile.OpenExisting ("Demo"); using MemoryMappedViewAccessor accessor = mmFile.CreateViewAccessor(); Console.WriteLine (accessor.ReadInt32 (0)); // 12345
Memory Mapped Named Writer
using MemoryMappedFile mmFile = MemoryMappedFile.CreateNew ("Demo", 500); using MemoryMappedViewAccessor accessor = mmFile.CreateViewAccessor(); accessor.Write (0, 12345); // In LINQPad, manaully stop the query to exit Console.ReadLine(); // Keep shared memory alive until user hits Enter.
Memory Mapped Reader
void Main() { // This can run in a separate executable: var file = Path.Combine (TempDirectory, "interprocess.bin"); using FileStream fs = new FileStream (file, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); using MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile (fs, null, fs.Length, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true); using MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(); Console.WriteLine (accessor.ReadInt32 (0)); // 12345 } static string TempDirectory { get => RuntimeInformation.IsOSPlatform (OSPlatform.Windows) ? @"C:\Temp" : "/tmp"; }
Memory Mapped Writer
void Main() { var file = Path.Combine(TempDirectory, "interprocess.bin"); File.WriteAllBytes (file, new byte [100]); using FileStream fs = new FileStream (file, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); using MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile (fs, null, fs.Length, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true); using MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(); accessor.Write (0, 12345); // In LINQPad, manaully stop the query to exit Console.ReadLine(); // Keep shared memory alive until user hits Enter. File.Delete(file); } static string TempDirectory { get => RuntimeInformation.IsOSPlatform (OSPlatform.Windows) ? @"C:\Temp" : "/tmp"; }
Memory Mapped Server
using (MemoryMappedFile mmFile = MemoryMappedFile.CreateNew ("Demo", 500)) using (MemoryMappedViewAccessor accessor = mmFile.CreateViewAccessor()) { accessor.Write (0, 12345); // In LINQPad, manaully stop the query to exit Console.ReadLine(); // Keep shared memory alive until user hits Enter. }
Memory Mapped Unsafe
void Main() { File.WriteAllBytes ("unsafe.bin", new byte [100]); var data = new Data { X = 123, Y = 456 }; using MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile ("unsafe.bin"); using MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(); accessor.Write (0, ref data); accessor.Read (0, out data); Console.WriteLine (data.X + " " + data.Y); // 123 456 unsafe { byte* pointer = null; try { accessor.SafeMemoryMappedViewHandle.AcquirePointer (ref pointer); int* intPointer = (int*)pointer; Console.WriteLine (*intPointer); // 123 } finally { if (pointer != null) accessor.SafeMemoryMappedViewHandle.ReleasePointer(); } } } struct Data { public int X, Y; }