Ebooks

C# input & output

This chapter is dedicated to input & output in C#. The input & output in C# is based on streams.

C# stream

A stream is an abstraction of a sequence of bytes, such as a file, an input/output device, an inter-process communication pipe, or a TCP/IP socket. Streams transfer data from one point to another point. Streams are also capable of manipulating the data; for example they can compress or encrypt the data. In the .NET Framework, the System.IO namespaces contain types that enable reading and writing on data streams and files.

C# provides higher-level methods for I/O operations in the File class and lower-level methods in classes such as StreamReader or StreamWriter.

Handling exceptions

I/O operations are error-prone. We can run into exceptions such as FileNotFoundException or UnauthorizedAccessException. Unlike Java, C# does not force the programmer to manually handle the exceptions. It is the programmer's decision whether to manually handle an exception. If the exception is not manually handled in the try/catch/finally contruct, the exception is handled by CLR.

Releasing resources

I/O resources must be released. The reources can be released manually in the finally clause with the Dispose() method. The using keyword can be used for automatic release of the resources. Also, the methods in the File class release the resources for us.

Example text file

In the examples, we use this simple text file:

thermopylae.txt
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.

C# File.ReadAllText

File provides static methods for the creation, copying, deletion, moving, and opening of a single file, and aids in the creation of FileStream objects.

Note: The File.ReadAllText() is not suited for reading very large files.

The File.ReadAllText() opens a file, reads all text in the file with the specified encoding, and then closes the file.

Program.cs
using System;
using System.IO;
using System.Text;

namespace ReadFileIntoString
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            var text = File.ReadAllText(path, Encoding.UTF8);
            Console.WriteLine(text);
        }
    }
}

The program reads the contents of the thermopylae.txt file and prints them to the console.

var text = File.ReadAllText(path, Encoding.UTF8);

We read the whole file into a string in one shot. In the second argument, we specify the encoding.

$ dotnet run
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.

This is the ouput.

C# File.ReadAllLines

File.ReadAllLines() opens a text file, reads all lines of the file into a string array, and then closes the file.

File.ReadAllLines() is a convenient method for reading files in C#. It should not be used when dealing with very large files.

Program.cs
using System;
using System.IO;

namespace ReadAllLines
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            string[] lines = File.ReadAllLines(path);
            
            foreach (string line in lines)
            {
                Console.WriteLine(line);
            }
        }
    }
}

The example reads all lines from a file into a string array. We go through the array in a foreach loop and print each line to the console.

C# create file

The File.CreateText() creates or opens a file for writing UTF-8 encoded text. If the file already exists, its contents are overwritten.

Program.cs
using System;
using System.IO;

namespace CreateFileEx
{
    class Program
    {
        static void Main()
        {
            var path = @"C:\Users\Jano\Documents\cars.txt";

            using (StreamWriter sw = File.CreateText(path))
            {
                sw.WriteLine("Hummer");
                sw.WriteLine("Skoda");
                sw.WriteLine("BMW");
                sw.WriteLine("Volkswagen");
                sw.WriteLine("Volvo");
            }
        }
    }
}

In the example, we create a cars.txt file and write some car names into it.

using (StreamWriter sw = File.CreateText(path))

The CreateText() method creates or opens a file for writing UTF-8 encoded text. It returns a StreamWriter object.

sw.WriteLine("Hummer");
sw.WriteLine("Skoda");
...

We write two lines to the stream.

$ cat C:\Users\Jano\Documents\cars.txt
Hummer
Skoda
BMW
Volkswagen
Volvo

We have successfully written five car names to the file.

C# create, last write, last access time

With the File class, we can get the creation, last write and last access times of a file. The Exists() method determines whether the specified file exists.

Program.cs
using System;
using System.IO;

namespace FileTimes
{
    class Program
    {
        static void Main(string[] args)
        {
           var path = @"C:\Users\Jano\Documents\cars.txt";

            if (File.Exists(path))
            {
                Console.WriteLine(File.GetCreationTime(path));
                Console.WriteLine(File.GetLastWriteTime(path));
                Console.WriteLine(File.GetLastAccessTime(path));
            }
        }
    }
}

If a specified file exists, we determine its creation, last write, and last access times.

if (File.Exists(path))

The Exists() method returns true if the caller has the required permissions and path contains the name of an existing file; otherwise, false. This method also returns false if path is null, an invalid path, or a zero-length string.

Console.WriteLine(File.GetCreationTime(path));
Console.WriteLine(File.GetLastWriteTime(path));
Console.WriteLine(File.GetLastAccessTime(path));

We get creation time, last write time and last access time of the specified file.

$ dotnet run
10/13/2019 1:59:03 PM
10/13/2019 1:59:03 PM
10/13/2019 1:59:03 PM

This is a sample output.

C# copy file

The File.Copy() method copies an existing file to a new file. It allows to overwrite a file of the same name.

Program.cs
using System;
using System.IO;

namespace CopyFileEx
{
    class Program
    {
        static void Main(string[] args)
        {
            var srcPath = @"C:\Users\Jano\Documents\cars.txt";
            var destPath = @"C:\Users\Jano\Documents\cars2.txt";

            File.Copy(srcPath, destPath, true);

            Console.WriteLine("File copied");
        }
    }
}

Then we copy the contents of the file to another file.

var srcPath = @"C:\Users\Jano\Documents\cars.txt";
var destPath = @"C:\Users\Jano\Documents\cars2.txt";

This is the source file and the destination file.

File.Copy(srcPath, destPath, true);

The Copy() method copies the file. The third parameter specifies if the file should be overwritten, if it exists.

$ dotnet run
File copied

This is a sample output.

C# IDisposable interface

Streams implement the IDisposable interface. Objects that implement this interface must be disposed manually at the earliest opportunity. This is done by calling the Dispose() method in the finally block or by utilizing the using statement.

Program.cs
using System;
using System.IO;

namespace ManualRelease
{
    class Program
    {
        static void Main(string[] args)
        {
            StreamReader sr = null;
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            try
            {
                sr = new StreamReader(path);
                Console.WriteLine(sr.ReadToEnd());
            }
            catch (IOException e)
            {
                Console.WriteLine("Cannot read file");
                Console.WriteLine(e.Message);

            }
            catch (UnauthorizedAccessException e)
            {
                Console.WriteLine("Cannot access file");
                Console.WriteLine(e.Message);

            }
            finally
            {
                 sr?.Dispose();
            }
        }
    }
}

In this example, we read characters from a file on a disk. We manually release allocated resources.

sr = new StreamReader(path);
Console.WriteLine(sr.ReadToEnd());

The StreamReader class is used to read characters. Its parent implements the IDisposable interface.

} catch (IOException e)
{
    Console.WriteLine("Cannot read file");
    Console.WriteLine(e.Message);

} catch (UnauthorizedAccessException e)
{
    Console.WriteLine("Cannot access file");
    Console.WriteLine(e.Message);
}

Possible exceptions are handled in the catch blocks.

finally
{
    sr?.Dispose();
}

In the finally block, the Dispose() method cleans up the resources. With the null-conditinal operator we call the method only if the variable is not null.

C# using statement

The using statement defines a scope at the end of which an object will be disposed. It provides a convenient syntax that ensures the correct use of IDisposable objects.

Program.cs
using System;
using System.IO;

namespace AutomaticCleanup
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            using (var sr = new StreamReader(path))
            {
                Console.WriteLine(sr.ReadToEnd());
            }
        }
    }
}

The example reads the contents of the thermopylae.txt file. The resources are released with the using statement. If we do not handle the IO exceptions, they will be handled by CLR.

C# MemoryStream

MemoryStream is a stream which works with data in a computer memory.

Program.cs
using System;
using System.IO;

namespace MemoryStreamEx
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var ms = new MemoryStream(6))
            {
                ms.WriteByte(9);
                ms.WriteByte(11);
                ms.WriteByte(6);
                ms.WriteByte(8);
                ms.WriteByte(3);
                ms.WriteByte(7);

                ms.Position = 0;

                int rs = ms.ReadByte();

                do
                {
                    Console.WriteLine(rs);
                    rs = ms.ReadByte();

                } while (rs != -1);
            }
        }
    }
}

We write six numbers to a memory with a MemoryStream. Then we read those numbers and print them to the console.

using (var ms = new MemoryStream(6))

The line creates and initializes a MemoryStream object with a capacity of six bytes.

ms.WriteByte(9);
ms.WriteByte(11);
ms.WriteByte(6);
...

The WriteByte() method writes a byte to the current stream at the current position.

ms.Position = 0;

We set the position of the cursor in the stream to the beginning using the Position property.

do
{
    Console.WriteLine(rs);
    rs = ms.ReadByte();

} while (rs != -1);

Here we read all bytes from the stream and print them to the console.

$ dotnet run
9
11
6
8
3
7

This is the output of the example.

C# StreamReader

StreamReader reads characters from a byte stream. It defaults to UTF-8 encoding.

Program.cs
using System;
using System.IO;

namespace ReadFileEx
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";
            
            using (var sr = new StreamReader(path))
            {
                while (sr.Peek() >= 0)
                {
                    Console.WriteLine(sr.ReadLine());
                }
            }
        }
    }
}

We read the contents of a file. This time we read the file line by line using the ReadLine() method.

while (sr.Peek() >= 0)
{
    Console.WriteLine(sr.ReadLine());
}

The Peek() method returns the next available character but does not consume it. It indicates whether we can call the ReadLine() method again. It returns -1 if there are no characters to be read.

C# count lines

In the next example, we will be counting lines.

Program.cs
using System;
using System.IO;

namespace CountingLines
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 0;
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            using (var sr = new StreamReader(path))
            {
                while (sr.ReadLine() != null)
                {
                    count++;
                }

                Console.WriteLine("There are {0} lines", count);
            }
        }
    }
}

We are counting lines in a file.

while(stream.ReadLine() != null)
{
    count++;
}

In the while loop, we read a line from the stream with the ReadLine() method. It returns a line from the stream or null if the end of the input stream is reached.

$ dotnet run
There are 3 lines

The file has three lines.

C# StreamWriter

StreamWriter writes characters to a stream in a particular encoding.

Program.cs
using System;
using System.IO;

namespace WriteToFile
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\newfile.txt";

            using (var sw = new StreamWriter(path))
            {
                sw.WriteLine("Today is a beautiful day.");
            }
        }
    }
}

The example writes a string to a file with StreamWriter.

using (var sw = new StreamWriter(path))

We create a new StreamWriter. The default enconding is UTF-8. The StreamWriter takes a path as a parameter. If the file exists, it is overwritten; otherwise, a new file is created.

$ dotnet run
$ cat C:\Users\Jano\Documents\newfile.txt
Today is a beautiful day.

We have shown the contents of the file with the cat command.

C# FileStream

FileStream provides a stream for a file, supporting both synchronous and asynchronous read and write operations.

StreamReader and StreamWriter work with text data, while FileStream works with bytes.

Program.cs
using System;
using System.IO;
using System.Text;

namespace FileStreamEx
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\newfile2.txt";

            using (var fs = new FileStream(path, FileMode.Append))
            {
                var text = "Фёдор Михайлович Достоевский";
                byte[] bytes = new UTF8Encoding().GetBytes(text);

                fs.Write(bytes, 0, bytes.Length);
            }
        }
    }
}

We write some text in Russian Cyrillic to a file.

using System.Text;

The UTF8Encoding class is located in the System.Text namespace.

using (var fs = new FileStream(path, FileMode.Append))

A FileStream object is created. The second parameter is a mode in which the file is opened. The append mode opens the file if it exists and seeks to the end of the file, or creates a new file.

var text = "Фёдор Михайлович Достоевский";

This is text in Russian Cyrillic.

byte[] bytes = new UTF8Encoding().GetBytes(text);

An array of bytes is created from the text in Russian Cyrillic.

fs.Write(bytes, 0, bytes.Length);

We write the bytes to the file stream.

$ cat C:\Users\Jano\Documents\newfile2.txt
Фёдор Михайлович Достоевский

We show the contents of the created file.

C# XmlTextReader

We can use streams to read XML data. The XmlTextReader is the class to read XML files in C#. The class is forward-only and read-only.

We have the following XML test file:

languages.xml
<?xml version="1.0" encoding="utf-8" ?>
<languages>
    <language>Python</language>
    <language>Ruby</language>
    <language>Javascript</language>
    <language>C#</language>
</languages>

This file contains language names between custom XML tags.

Program.cs
using System;
using System.IO;
using System.Xml;

namespace ReadingXMLFile
{
    public class Program
    {
        static void Main()
        {
            string path = @"C:\Users\Jano\Documents\languages.xml";

            using (var xreader = new XmlTextReader(path))
            {
                xreader.MoveToContent();

                while (xreader.Read())
                {
                    var node = xreader.NodeType switch
                    {
                        XmlNodeType.Element => String.Format("{0}: ", xreader.Name),
                        XmlNodeType.Text => String.Format("{0} \n", xreader.Value),
                        _ => ""
                    };

                    Console.Write(node);
                }
            }
        }
    }
}

This example reads data from the XML file and prints it to the terminal.

using System.Xml;

The System.Xml namespace contains classes related to Xml reading and writing.

using (var xreader = new XmlTextReader(path))

An XmlTextReader object is created. It is a reader that provides fast, non-cached, forward-only access to XML data. It takes the file name as a parameter.

xreader.MoveToContent();

The MoveToContent() method moves to the actual content of the XML file.

while (xreader.Read())

This line reads the next node from the stream. The Read() method returns false if there are no more nodes left.

var node = xreader.NodeType switch
{
    XmlNodeType.Element => String.Format("{0}: ", xreader.Name),
    XmlNodeType.Text => String.Format("{0} \n", xreader.Value),
    _ => ""
};

Console.Write(node);

Here we print the element name and the element text.

$ dotnet run
language: Python
language: Ruby
language: Javascript
language: C#

This is the output of example.

C# create, move directory

The System.IO.Directory is a class that has static methods for creating, moving, and enumerating through directories and subdirectories.

Program.cs
using System;
using System.IO;

namespace DirectoryEx
{
    class Program
    {
        static void Main(string[] args)
        {
            Directory.CreateDirectory("temp");
            Directory.CreateDirectory("newdir");
            Directory.Move("temp", "temporary");
        }
    }
}

We create two directories and rename one of the created ones. The directories are created in the project folder.

Directory.CreateDirectory("temp");

The CreateDirectory() method creates a new directory.

Directory.Move("temp", "temporary");

The Move() method gives a specified directory a new name.

C# DirectoryInfo

DirectoryInfo exposes instance methods for creating, moving, and enumerating through directories and subdirectories.

Program.cs
using System;
using System.IO;

namespace ShowContents
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents";
            var dirInfo = new DirectoryInfo(path);

            string[] files = Directory.GetFiles(path);
            DirectoryInfo[] dirs = dirInfo.GetDirectories();

            foreach (DirectoryInfo subDir in dirs)
            {
                Console.WriteLine(subDir.Name);
            }

            foreach (string fileName in files)
            {
                Console.WriteLine(fileName);
            }
        }
    }
}

We use the DirectoryInfo class to traverse a specific directory and print its contents.

var path = @"C:\Users\Jano\Documents";
var DirInfo = new DirectoryInfo(path);

We show the contents of the specified directory.

string[] files = Directory.GetFiles(path);;

We get all files of the directory using the static GetFiles() method.

DirectoryInfo[] dirs = dir.GetDirectories();

We get all the directories.

foreach (DirectoryInfo subDir in dirs)
{
    Console.WriteLine(subDir.Name);
}

Here we loop through directories and print their names to the console.

foreach (string fileName in files)
{
    Console.WriteLine(fileName);
}

Here we loop through the array of files and print their names to the console.

In this chapter, we have covered Input/Output operations in C#.