cs11dotnet7/notebooks/Chapter09.dib

676 lines
15 KiB
Plaintext
Raw Permalink Normal View History

2022-02-09 11:30:33 +01:00
#!markdown
# Chapter 9 - Working with Files, Streams, and Serialization
- Managing the filesystem
- Reading and writing with streams
- Encoding and decoding text
- Serializing object graphs
- Controlling JSON processing
#!markdown
# Managing the filesystem
Your applications will often need to perform input and output operations with files and
directories in different environments. The `System` and `System.IO` namespaces contain classes
for this purpose.
#!markdown
## Handling cross-platform environments and filesystems
#!csharp
using static System.Console;
using static System.IO.Directory;
using static System.IO.Path;
using static System.Environment;
#!csharp
using System.IO;
#!csharp
WriteLine("{0,-33} {1}", arg0: "Path.PathSeparator",
arg1: PathSeparator);
WriteLine("{0,-33} {1}", arg0: "Path.DirectorySeparatorChar",
arg1: DirectorySeparatorChar);
WriteLine("{0,-33} {1}", arg0: "Directory.GetCurrentDirectory()",
arg1: GetCurrentDirectory());
WriteLine("{0,-33} {1}", arg0: "Environment.CurrentDirectory",
arg1: CurrentDirectory);
WriteLine("{0,-33} {1}", arg0: "Environment.SystemDirectory",
arg1: SystemDirectory);
WriteLine("{0,-33} {1}", arg0: "Path.GetTempPath()",
arg1: GetTempPath());
WriteLine("GetFolderPath(SpecialFolder");
WriteLine("{0,-33} {1}", arg0: " .System)",
arg1: GetFolderPath(SpecialFolder.System));
WriteLine("{0,-33} {1}", arg0: " .ApplicationData)",
arg1: GetFolderPath(SpecialFolder.ApplicationData));
WriteLine("{0,-33} {1}", arg0: " .MyDocuments)",
arg1: GetFolderPath(SpecialFolder.MyDocuments));
WriteLine("{0,-33} {1}", arg0: " .Personal)",
arg1: GetFolderPath(SpecialFolder.Personal));
#!markdown
## Managing drives
#!csharp
WriteLine("{0,-30} | {1,-10} | {2,-7} | {3,18} | {4,18}",
"NAME", "TYPE", "FORMAT", "SIZE (BYTES)", "FREE SPACE");
foreach (DriveInfo drive in DriveInfo.GetDrives())
{
if (drive.IsReady)
{
WriteLine(
"{0,-30} | {1,-10} | {2,-7} | {3,18:N0} | {4,18:N0}",
drive.Name, drive.DriveType, drive.DriveFormat,
drive.TotalSize, drive.AvailableFreeSpace);
}
else
{
WriteLine("{0,-30} | {1,-10}", drive.Name, drive.DriveType);
}
}
#!markdown
## Managing directories
#!csharp
// define a directory path for a new folder
// starting in the user's folder
string newFolder = Combine(
GetFolderPath(SpecialFolder.Personal),
"Code", "Chapter09", "NewFolder");
WriteLine($"Working with: {newFolder}");
// check if it exists
WriteLine($"Does it exist? {Exists(newFolder)}");
// create directory
WriteLine("Creating it...");
CreateDirectory(newFolder);
WriteLine($"Does it exist? {Exists(newFolder)}");
Write("Confirm the directory exists, and then execute the next notebook cell.");
#!csharp
// delete directory
WriteLine("Deleting it...");
Delete(newFolder, recursive: true);
WriteLine($"Does it exist? {Exists(newFolder)}");
#!markdown
## Managing files
#!csharp
// define a directory path to output files
// starting in the user's folder
string dir = Combine(
GetFolderPath(SpecialFolder.Personal),
"Code", "Chapter09", "OutputFiles");
CreateDirectory(dir);
// define file paths
string textFile = Combine(dir, "Dummy.txt");
string backupFile = Combine(dir, "Dummy.bak");
WriteLine($"Working with: {textFile}");
// check if a file exists
WriteLine($"Does it exist? {File.Exists(textFile)}");
// create a new text file and write a line to it
StreamWriter textWriter = File.CreateText(textFile);
textWriter.WriteLine("Hello, C#!");
textWriter.Close(); // close file and release resources
WriteLine($"Does it exist? {File.Exists(textFile)}");
// copy the file, and overwrite if it already exists
File.Copy(sourceFileName: textFile,
destFileName: backupFile, overwrite: true);
WriteLine(
$"Does {backupFile} exist? {File.Exists(backupFile)}");
Write("Confirm the files exist, and then run the next notebook cell.");
#!csharp
// delete file
File.Delete(textFile);
WriteLine($"Does it exist? {File.Exists(textFile)}");
// read from the text file backup
WriteLine($"Reading contents of {backupFile}:");
StreamReader textReader = File.OpenText(backupFile);
WriteLine(textReader.ReadToEnd());
textReader.Close();
#!markdown
## Managing paths
#!csharp
// Managing paths
WriteLine($"Folder Name: {GetDirectoryName(textFile)}");
WriteLine($"File Name: {GetFileName(textFile)}");
WriteLine("File Name without Extension: {0}",
GetFileNameWithoutExtension(textFile));
WriteLine($"File Extension: {GetExtension(textFile)}");
WriteLine($"Random File Name: {GetRandomFileName()}");
WriteLine($"Temporary File Name: {GetTempFileName()}");
#!markdown
## Getting file information
#!csharp
FileInfo info = new(backupFile);
WriteLine($"{backupFile}:");
WriteLine($"Contains {info.Length} bytes");
WriteLine($"Last accessed {info.LastAccessTime}");
WriteLine($"Has readonly set to {info.IsReadOnly}");
#!markdown
## Controlling how you work with files
#!csharp
FileInfo info = new(backupFile);
WriteLine("Is the backup file compressed? {0}",
info.Attributes.HasFlag(FileAttributes.Compressed));
#!markdown
# Reading and writing with streams
#!markdown
## Writing to text streams
#!csharp
static class Viper
{
// define an array of Viper pilot call signs
public static string[] Callsigns = new[]
{
"Husker", "Starbuck", "Apollo", "Boomer",
"Bulldog", "Athena", "Helo", "Racetrack"
};
}
#!csharp
// define a file to write to
string textFile = Combine(CurrentDirectory, "streams.txt");
// create a text file and return a helper writer
StreamWriter text = File.CreateText(textFile);
// enumerate the strings, writing each one
// to the stream on a separate line
foreach (string item in Viper.Callsigns)
{
text.WriteLine(item);
}
text.Close(); // release resources
// output the contents of the file
WriteLine("{0} contains {1:N0} bytes.",
arg0: textFile,
arg1: new FileInfo(textFile).Length);
WriteLine(File.ReadAllText(textFile));
#!markdown
## Writing to XML streams
#!csharp
using System.Xml;
#!csharp
#nullable enable
FileStream? xmlFileStream = null;
XmlWriter? xml = null;
try
{
// define a file to write to
string xmlFile = Combine(CurrentDirectory, "streams.xml");
// create a file stream
xmlFileStream = File.Create(xmlFile);
// wrap the file stream in an XML writer helper
// and automatically indent nested elements
xml = XmlWriter.Create(xmlFileStream,
new XmlWriterSettings { Indent = true });
// write the XML declaration
xml.WriteStartDocument();
// write a root element
xml.WriteStartElement("callsigns");
// enumerate the strings writing each one to the stream
foreach (string item in Viper.Callsigns)
{
xml.WriteElementString("callsign", item);
}
// write the close root element
xml.WriteEndElement();
// close helper and stream
xml.Close();
xmlFileStream.Close();
// output all the contents of the file
WriteLine("{0} contains {1:N0} bytes.",
arg0: xmlFile,
arg1: new FileInfo(xmlFile).Length);
WriteLine(File.ReadAllText(xmlFile));
}
catch (Exception ex)
{
// if the path doesn't exist the exception will be caught
WriteLine($"{ex.GetType()} says {ex.Message}");
}
finally
{
if (xml != null)
{
xml.Dispose();
WriteLine("The XML writer's unmanaged resources have been disposed.");
if (xmlFileStream != null)
{
xmlFileStream.Dispose();
WriteLine("The file stream's unmanaged resources have been disposed.");
}
}
}
#!markdown
## Simplifying disposal by using the using statement
#!csharp
using FileStream file2 = File.OpenWrite(
Path.Combine(CurrentDirectory, "file2.txt"));
using StreamWriter writer2 = new(file2);
try
{
writer2.WriteLine("Welcome, .NET!");
}
catch(Exception ex)
{
WriteLine($"{ex.GetType()} says {ex.Message}");
}
#!markdown
## Compressing streams
#!csharp
using System.IO.Compression; // BrotliStream, GZipStream, CompressionMode
#!csharp
static void WorkWithCompression(bool useBrotli = true)
{
string fileExt = useBrotli ? "brotli" : "gzip";
// compress the XML output
string filePath = Combine(
CurrentDirectory, $"streams.{fileExt}");
FileStream file = File.Create(filePath);
Stream compressor;
if (useBrotli)
{
compressor = new BrotliStream(file, CompressionMode.Compress);
}
else
{
compressor = new GZipStream(file, CompressionMode.Compress);
}
using (compressor)
{
using (XmlWriter xml = XmlWriter.Create(compressor))
{
xml.WriteStartDocument();
xml.WriteStartElement("callsigns");
foreach (string item in Viper.Callsigns)
{
xml.WriteElementString("callsign", item);
}
}
} // also closes the underlying stream
// output all the contents of the compressed file
WriteLine("{0} contains {1:N0} bytes.",
filePath, new FileInfo(filePath).Length);
WriteLine($"The compressed contents:");
WriteLine(File.ReadAllText(filePath));
// read a compressed file
WriteLine("Reading the compressed XML file:");
file = File.Open(filePath, FileMode.Open);
Stream decompressor;
if (useBrotli)
{
decompressor = new BrotliStream(
file, CompressionMode.Decompress);
}
else
{
decompressor = new GZipStream(
file, CompressionMode.Decompress);
}
using (decompressor)
{
using (XmlReader reader = XmlReader.Create(decompressor))
{
while (reader.Read())
{
// check if we are on an element node named callsign
if ((reader.NodeType == XmlNodeType.Element)
&& (reader.Name == "callsign"))
{
reader.Read(); // move to the text inside element
WriteLine($"{reader.Value}"); // read its value
}
}
}
}
}
#!csharp
WorkWithCompression();
WorkWithCompression(useBrotli: false);
#!markdown
# Encoding and decoding text
Change the `ConsoleKey` value to D1 to D5 to select the encoding that you want to use.
#!csharp
// choose an encoding
Write("Press a number to choose an encoding: ");
ConsoleKey number = ConsoleKey.D3; // ReadKey(intercept: false).Key;
WriteLine();
WriteLine();
Encoding encoder = number switch
{
ConsoleKey.D1 => Encoding.ASCII,
ConsoleKey.D2 => Encoding.UTF7,
ConsoleKey.D3 => Encoding.UTF8,
ConsoleKey.D4 => Encoding.Unicode,
ConsoleKey.D5 => Encoding.UTF32,
_ => Encoding.Default
};
// define a string to encode
string message = "Café cost: £4.39";
// encode the string into a byte array
byte[] encoded = encoder.GetBytes(message);
// check how many bytes the encoding needed
WriteLine("{0} uses {1:N0} bytes.",
encoder.GetType().Name, encoded.Length);
WriteLine();
// enumerate each byte
WriteLine($"BYTE HEX CHAR");
foreach (byte b in encoded)
{
WriteLine($"{b,4} {b.ToString("X"),4} {(char)b,5}");
}
// decode the byte array back into a string and display it
string decoded = encoder.GetString(encoded);
WriteLine(decoded);
#!markdown
# Serializing object graphs
Serialization is the process of converting a live object into a sequence of bytes using a specified
format. Deserialization is the reverse process.
#!csharp
#nullable enable
public class Person
{
public Person() { } // required to use XmlSerializer
public Person(decimal initialSalary)
{
Salary = initialSalary;
}
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public HashSet<Person>? Children { get; set; }
protected decimal Salary { get; set; }
}
#!csharp
using System.Xml.Serialization; // XmlSerializer
#!csharp
// create an object graph
List<Person> people = new()
{
new(30000M)
{
FirstName = "Alice",
LastName = "Smith",
DateOfBirth = new(1974, 3, 14)
},
new(40000M)
{
FirstName = "Bob",
LastName = "Jones",
DateOfBirth = new(1969, 11, 23)
},
new(20000M)
{
FirstName = "Charlie",
LastName = "Cox",
DateOfBirth = new(1984, 5, 4),
Children = new()
{
new(0M)
{
FirstName = "Sally",
LastName = "Cox",
DateOfBirth = new(2000, 7, 12)
}
}
}
};
#!csharp
// create object that will format a List of Persons as XML
XmlSerializer xs = new(people.GetType());
WriteLine(people.GetType());
// create a file to write to
string path = Combine(CurrentDirectory, "people.xml");
WriteLine(path);
using (FileStream stream = File.Create(path))
{
// serialize the object graph to the stream
xs.Serialize(stream, people);
}
WriteLine("Written {0:N0} bytes of XML to {1}",
arg0: new FileInfo(path).Length,
arg1: path);
WriteLine();
// Display the serialized object graph
WriteLine(File.ReadAllText(path));
#!markdown
## Serializing with JSON
#!csharp
#r "nuget:Newtonsoft.Json,13.0.1"
#!csharp
// create a file to write to
string jsonPath = Combine(CurrentDirectory, "people.json");
using (StreamWriter jsonStream = File.CreateText(jsonPath))
{
// create an object that will format as JSON
Newtonsoft.Json.JsonSerializer jss = new();
// serialize the object graph into a string
jss.Serialize(jsonStream, people);
}
WriteLine();
WriteLine("Written {0:N0} bytes of JSON to: {1}",
arg0: new FileInfo(jsonPath).Length,
arg1: jsonPath);
// Display the serialized object graph
WriteLine(File.ReadAllText(jsonPath));
#!csharp
using NewJson = System.Text.Json.JsonSerializer;
#!csharp
using (FileStream jsonLoad = File.Open(jsonPath, FileMode.Open))
{
// deserialize object graph into a List of Person
List<Person>? loadedPeople =
await NewJson.DeserializeAsync(utf8Json: jsonLoad,
returnType: typeof(List<Person>)) as List<Person>;
if (loadedPeople is not null)
{
foreach (Person p in loadedPeople)
{
WriteLine("{0} has {1} children.",
p.LastName, p.Children?.Count ?? 0);
}
}
}
#!markdown
## Controlling JSON processing
#!csharp
using System.Text.Json; // JsonSerializer
using System.Text.Json.Serialization; // [JsonInclude]
#!csharp
#nullable enable
public class Book
{
// constructor to set non-nullable property
public Book(string title)
{
Title = title;
}
// properties
public string Title { get; set; }
public string? Author { get; set; }
// fields
[JsonInclude] // include this field
public DateOnly PublishDate;
[JsonInclude] // include this field
public DateTimeOffset Created;
public ushort Pages;
}
#!csharp
Book csharp10 = new(title:
"C# 10 and .NET 6 - Modern Cross-platform Development")
{
Author = "Mark J Price",
PublishDate = new(year: 2021, month: 11, day: 9),
Pages = 823,
Created = DateTimeOffset.UtcNow,
};
JsonSerializerOptions options = new()
{
IncludeFields = true, // includes all fields
PropertyNameCaseInsensitive = true,
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
string filePath = Combine(CurrentDirectory, "book.json");
using (Stream fileStream = File.Create(filePath))
{
JsonSerializer.Serialize<Book>(
utf8Json: fileStream, value: csharp10, options);
}
WriteLine("Written {0:N0} bytes of JSON to {1}",
arg0: new FileInfo(filePath).Length,
arg1: filePath);
WriteLine();
// Display the serialized object graph
WriteLine(File.ReadAllText(filePath));