It’s time to learn how to work with files in C#. For that purpose, in this article, we will cover two classes StreamWriter and StreamReader.


For the complete navigation of this series check out: C# Back to Basics.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

If you want to download the source code for our examples, you can do that from here StreamWriter and StreamReader C# Source Code.

The StreamReader and StreamWriter classes enable the reading and writing actions to a file. Both of these classes exist in the System.IO namespace as well as many other classes for working with files and directories.

We are going to cover the following sections:

Creating Objects for StreamWriter and StreamReader

To create objects for the StreamReader and StreamWriter classes we need to use the standard initialization for the reference data types. We can execute this initialization in a couple of ways but the most common is by only providing an address to the file:

StreamReader readerRelativePath = new StreamReader("test.txt");
StreamReader readerAbsolutePath = new StreamReader("C:\\MyProject\\test.txt");
StreamWriter writerRelativePath = new StreamWriter("test.txt");
StreamWriter writerAbsolutePath = new StreamWriter("C:\\MyProject\\test.txt");

As we can see from the code above, we can provide the relative or absolute path to our file. If we provide a relative path (just a name and extension) Visual Studio will place a file inside the projectName/bin/debug folder.

StreamReader Methods

StreamReader contains many different methods to work with files but we are going to mention few of those.

The Read() method will return next sign as an integer number or -1 if we reached the end of the file. We can use explicit conversion (cast) to convert that integer into a char type:

static void Main(string[] args)
{
    StreamReader sr = new StreamReader("test.txt");

    int x;
    char ch;

    x = sr.Read();
            
    while(x != -1)
    {
        ch = (char)x;
        //do stuff here
        x = sr.Read();
    }
}

The ReadLine() method will return a whole line as a string. If we reached the end of the file it will return null:

static void Main(string[] args)
{
    StreamReader sr = new StreamReader("test.txt");

    string line = sr.ReadLine();

    while(line != null)
    {
        //some coding
        line = sr.ReadLine();
    }
}

The ReadToEnd() method returns a whole file in one string. If there is nothing more to read it will return an empty string.

The Peek() method checks the next character in the file or if it finds nothing it will return -1:

static void Main(string[] args)
{
    StreamReader sr = new StreamReader("test.txt");
    string line;

    while(sr.Peek() != -1)
    {
        line = sr.ReadLine();
        //some coding
    }
}

StreamWriter Methods

The two most important methods for the StreamWriter class is the Write() and WriteLine(). With the Write() method we write a line inside a file but without moving to another line after. But with the WriteLine() method we write a line inside a file and moving to another line.

It is very important to call the Close() method, after we are finished with using reader or writer objects.

Example1: Create an application that writes five random numbers from 1 to 100 to a file named numbers.txt. Then it will read all the numbers from that file, print them out and print the maximum number:

class Program
{
    public static void WriteToFile(string path)
    {
        StreamWriter sw = new StreamWriter(path);
        Random r = new Random(); //class to generate random numbers
        for(int i = 1; i <= 5; i++)
        {
            sw.WriteLine(r.Next(1,101));
        }

        sw.Close();
    }

    public static void PrintNumbersAndMax(string path)
    {
        StreamReader sr = new StreamReader(path);
        string line = sr.ReadLine();
        Console.WriteLine(line);
        int max = Convert.ToInt32(line);

        while ((line = sr.ReadLine()) != null)
        {
            Console.WriteLine(line);

            int temp = Convert.ToInt32(line);
            if(temp > max)
            {
                max = temp;
            }
        }
     
        sr.Close();

        Console.WriteLine($"Max number is: {max}");
    }

    static void Main(string[] args)
    {
        WriteToFile("numbers.txt");

        PrintNumbersAndMax("numbers.txt");

        Console.ReadLine();
   }
}

As we can see, we have to use the Close method to close our reader and writer. But there is an even better way to do this. By using the using block.

Using Block

The using block helps to manage our resources. It specifies a scope in which we use our resource, and once we leave that scope, the resource is going to be managed.

To use the using block we need to specify the using keyword, create resources inside parentheses and declare the scope of the using block with the curly brackets:

using (Resource creation)
{

}

So, we can rewrite one of our methods form the previous example:

public static void PrintNumbersAndMax(string path)
{
    using (StreamReader sr = new StreamReader(path))
    {
        string line = sr.ReadLine();
        Console.WriteLine(line);
        int max = Convert.ToInt32(line);

        while ((line = sr.ReadLine()) != null)
        {
            Console.WriteLine(line);

            int temp = Convert.ToInt32(line);
            if (temp > max)
            {
                 max = temp;
            }
        }

        Console.WriteLine($"Max number is: {max}");
    }
}

In this example, we are not using the Close method because as soon as execution leaves the body of the using statement, the StreamReader object is going to be managed.

Conclusion

There we go. Right now, we have a good knowledge to manipulate files from C#.

In the next article, we are going to learn how to use File and Directory classes to manipulate files in C#.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!