Day 15 Programming in C# 70-483
Index
- Introduction
- Files directories and drives
- Streams
- Network
- Async IO streams
- References
Introduction
This post is part of a
series of post to help you prepare the MCSD certification, particularly the certification exam 70-483, based on the book:
You will find all the code available in the following
GitHub repository. Lets talk a little bit about threads and how they work using a .Net development environment.
Files directories and drives
Working with the System.IO namespace you'll find a lot of classes related with files, paths, drives, folders. See the main classes in this list:
- File
- Exists(string path)
- Delete(string path)
- Move(string source, string destination)
- Copy(path, destPath)
- FileInfo
- Exists: property
- Delete()
- MoveTo(string destination)
- CopyTo(destPath)
- Path static class within the System.IO namespace
- Combine(folder, fileName): returns a string with the full path for that file.
- GetDirectoryName(path)
- GetExtension(path)
- GetFileName(path)
- GetPathRoot(path)
- DriveInfo: it enumerates the current drives and display some information about them
- GetDrives()
- Name / DriveType / VolumeLabel / DriveFormat
- Directory
- CreateDirectory()
- GetFiles(string path)
- DirectoryInfo (multiple operations against a folder)
- Create()
- GetDirectories(): searchPattern can be passed as a parameter to reduce the amount of directories retrieved.
- EnumerateDirectories(): with GetDirectories you get a list and you need to wait until is completely retrieved but with this you can start enumerating the directories before been completly retrieved.
- MoveTo()
- Move()
- GetFiles();
- SearchOptoin: specify a search pattern to be performed agains a Directory/DirectoryInfo object
- wildcards: * stands for any group of characters, ? stands for any single character.
- DirectorySecurity: grant access to folders, hosted in System.Security.AccessControl namespace.
Insufficient privileges creating a folder can end up throwing a UnathorizedAccessException and trying to remove a folder that doesn't exists in a DirectoryNotFoundException.
Streams
Streams refers to abstractions of a sequence of bytes, which means, if you want to save text first you need to transform that text into an array of bytes. Types of streams:
- Reading
- Writing: when writing to a FileStream
- Seeking: stands for some streams have the concept of a current position.
To convert between bytes and strings we use the System.Text.Enconding class, it uses different standards to make the transformation like:
- UTF-8
- ASCII
- BigEndianUnicode
- Unicode
- UTF32
- UTF7
To follow this process in an easier way, the File class supports a CreateText() method which returns a StreamWriter object that we can use to perform write operations directly to a file. To read the data you can use a FileStream object, you would need to read every byte one by one by using the ReadByte() method and finally, translate those bytes using a particular encoding like UTF8 calling the method: Encoding.UTF8.GetString(data) which returns a string. The alternative, if you know you are reading a piece of text, is the StreamReader and call the method ReadLine(). See example below:
using System.IO;
using System.Text;
namespace Chapter4
{
public static class CompareFileStreamReader
{
public static void HowToReadAFile()
{
string path = @"c:\temp\test.txt";
// read a file using a FileStream object
using (FileStream fileStream = File.OpenRead(path))
{
byte[] data = new byte[fileStream.Length];
for (int index = 0; index < fileStream.Length; index++)
{
data[index] = (byte)fileStream.ReadByte();
}
System.Console.WriteLine(Encoding.UTF8.GetString(data));
}
// read a file using a StreamReader object
using(StreamReader reader = File.OpenText(path))
{
System.Console.WriteLine(reader.ReadLine());
}
}
}
}
Working with multiple streams together (
decorator pattern) can be handy sometimes, specially when compressing/decompressing streams (FileStream, MemoryStream,...) using the
GZipStream class within the
System.IO.Compression namespace.
Another interesting class is BufferedStream, which comes handy when you need to read/write big chunks of data (which is what drives are optimized for, instead of reading byte by byte). It wraps a FileStream object and works out if it's possible to read larger chunks of data at once.
To finish this chapter about streams, I want to highlight how files can be locked by the OS and you might think the file was deleted. For example a File.Exists call can return false and we assume there's no such a file, while in reality, the file is in use by another thread. This is important to consider when writing/reading resources and always wrap our code up with try/catch blocks. Typically we want to focus on DirectoryNotFoundException and FileNotFoundException.
Network
The .Net framework enable your apps to communicate over the network. It's then when the System.Net namespace turns interesting and the WebRequest and WebResponse classes vital. There are specific implementations by protocol like HttpWebRequest and HttpWebResponse based on the protocol HTTP. See an usage example here:
Async IO streams
All the classes showed in this post until now are synchronous classes. Working with resources, typically, implies some delays we need to take in consideration. If not we can make our apps looks unresponsive (it happens when an app looks freezes and the user can't perform any action).
C#5 introduced async/await keywords, this will make the compiler responsible for turning your "synchronous-looking" code into a state machine that handles all possible situations. Many methods have an async equivalent that returns Task or Task<T>, if there's nothing in return or if the method returns a type (T), respectively. The use of this async methods will make your apps more responsible and scalable.
Real async makes sure your thread can do other work until the OS notifies the I/O is ready. Bear in mind not to use the await operator multiple times within the same function if the async calls can be run in parallel. The following example shows you how you need to call the await operator at the end of your functions to just wait the minimum possible time, in this case, wait only for the longest call, instead of for each individual.