C# FileStream For Lazy Developers In A Hurry. The Bare Minimum
Tags: csharp, beginners, codenewbie, webdev
Published: 2023-02-28 21:19:07

If you:
* Have no idea what a stream is
* Have a blurry idea but need a better analogy
* Don't know how to use(understand) streams in actual code

This post is for you.

This is my brain trying to understand streams last week.

## What Are Streams?

A Stream is the flow of data(bytes) that moves from one point to another, just like a stream of water flowing down a river.

Streams are used to read and write data to and from: 
* Files
* Networks
* Memory
* Other streams

## An Analogy To Help Us Navigate The Examples

![water tank analogy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wktmogvnp49yre1j0qeb.png)

Imagine a huge tank of muddy water(you don't know how much water is in there) and you need to clean this water and fill another tank with clean water.

This tank has a faucet on the outside that you can open and let the muddy water flow out.

You find a hose that has a water filter in it.

You connect the hose to the muddy water tank and put the other end in the clean water tank.

As you turn on the tap, the water flows through the hose, is filtered and clean water comes out. 

* The stream is the flow of water(not the water itself).
* The muddy tank is the source you are trying to read(file in our case).
* The filter is a helper class that helps us clean the water(convert bytes to text).
* The clean water tank is like the output on the other end(in our case we'll be logging the file content to the console as it comes)

![reading from file analogy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mj2hoyeaptg035fifsbc.png)

## C# classes to work with streams

These are the classes to use when reading and writing to and from different sources like files, memory, network, other streams, etc(I don't know what else).

![stream hierarchy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/klzglildihwp5p1rh4ph.png)

**Stream** is the abstract class that provides methods to transfer bytes to and from the source(file, memory, network, etc). It is the **core** class to transfer bytes. 

If a class wants to read or write from a source(file, memory, or network) it needs to implement the Stream class.

We'll only focus on the **FileStream** and the helper classes **StreamWriter** and **StreamReader** in this post.

## How To Read a File Using the **FileStream**
Let's create a simple method that takes a string path and reads the content of the file and outputs it to the console

```csharp
using System.IO;

public static void ReadFile(string path) //E.g. @"d:\text.txt"
{
       var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
       var buffer = new byte[fileStream .Length];
       fileStream.Read(buffer);
       var result = Encoding.UTF8.GetString(buffer);
       Console.WriteLine(result);
       
       fileStream.Flush(); // ensure that any data still in the buffer(bytesArray) is written to the file
       fileStream.Close(); //release any system resources associated with the object
}
```
* The file path is the muddy water in the tank(but instead of mud we have bytes)
* The FileStream object is the muddy water tank's faucet(you open and bytes flow out instead of muddy water)
* The Encoding.UTF8.GetString() method is the filter cleaning the muddy water. In this case, it converts bytes into text.

A simplified way to write this method is with the **using** block. When the variable you declared inside of the **using** falls out of scope, it calls the .Dispose() method which internally calls .Flush() and .Close() automatically for us.

```csharp
using System.IO;

public static void ReadFile(string path) //E.g. @"d:\text.txt"
{
       using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
       {
           var bytesArray = new byte[fileStream.Length];
           fileStream.Read(bytesArray);
           var result = Encoding.UTF8.GetString(bytesArray);
           Console.WriteLine(result);
       }
}
```
What if I don't want to deal with bytes directly is there a way to make this more high-level?

Here is where the helper class **StreamReader** comes in.
Have a look at the same code as above but using StreamReader instead:

```csharp
using System.IO;

public static void ReadFile(string path) //E.g. @"d:\text.txt"
{
    using (var streamReader = new StreamReader(path))
    {
        var result = streamReader.ReadToEnd();
        Console.WriteLine(result);
    }
}
```
Note: This is only one way of doing it. There are others, e.g. `File.ReadAllText(path)`.

In the above example, we don't even need to use the `FileStream`. The `StreamReader` deals with the `FileStream` under the hood to make our lives simpler.

Let's have a look at another example.

What if we are reading a file that is bigger than our available RAM? 

One thing is for sure, we cannot read the whole file in memory at once! We have to use the Hannibal Lecter approach and do it piece by piece(forgive my analogy)

Here is one way you can do it(Lower level version):
```csharp
using System.IO;

public static string ReadLargeFile(string path)
{
    const int bufferSize = 4096; // read the file in 4KB chunks
    var builder = new StringBuilder();

    using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
    using (var streamReader = new StreamReader(fileStream))
    {
        char[] buffer = new char[bufferSize];
        int bytesRead;

        while ((bytesRead = streamReader.ReadBlock(buffer, 0, bufferSize)) > 0)
        {
            builder.Append(buffer, 0, bytesRead);
        }
    }

    return builder.ToString();
}
```

Note that in the above example, we use one of the `StreamReader` constructors that take a `FileStream` as a parameter. We do that because we need more control over the stream in this case.

Can we do away with the ugly lower-level code?
Yes, here we go:
```csharp
using System.IO;

public static string ReadLargeFile(string path)
{
    StringBuilder sb = new StringBuilder();

    using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
    using (StreamReader streamReader = new StreamReader(fileStream))
    {
        string line;
        while ((line = streamReader.ReadLine()) != null)
        {
            sb.AppendLine(line);
        }
    }

    return sb.ToString();
}
```
In the above case:
* We are not dictating the size of the buffer to be read at a time(just do it line by line, however much that is)
* we don't have to keep track of the bytes read.

It is much simpler to use.

What about writing a file? Let's have a look next.


## How To Write A File Using The **FileStream**

This simple method takes a string path, creates a file a writes some content to it.

Using the `FileStream`

```csharp
using System.IO;

public static void WriteText(string path) //E.g. @"d:\text.txt"
{
   string text = "hey my dude!";
   byte[] bytesArray = Encoding.UTF8.GetBytes(text);

   using (var fileStream = new FileStream(path, FileMode.Create))
   {
      fileStream.Write(bytesArray);     
   }
}
```

Using the `StreamWriter` helper:
```csharp
using System.IO;

public static void WriteText(string path) //E.g. @"d:\text.txt"
{
    using (var s = new StreamWriter(path))
    {
        s.WriteLine(text);
    }
}
```
Note: The `File` class provides some handy methods as well. The above example could've been written as: `File.WriteAllText(path, text);`

Let's raise the same size problem again.

What if you want to write a big ass file that is too large to fit in your RAM at once?

You can use the same break it up as you write the same way we did when reading:

Using the `FileReader` only:

```csharp
public string WriteFile(string path, string content)
{
    // Create a new file at the specified path
    using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write))
    {
        // Write the content to the file in chunks
        byte[] buffer = Encoding.UTF8.GetBytes(content);
        int chunkSize = 1024;
        for (int i = 0; i < buffer.Length; i += chunkSize)
        {
            int remainingBytes = buffer.Length - i;
            int bytesToWrite = remainingBytes < chunkSize ? remainingBytes : chunkSize;
            stream.Write(buffer, i, bytesToWrite);
        }
    }
}
```

Using `FileReader` and `StreamWriter`:
```csharp
public static void WriteLargeFile(string path, string content)
{
    int bufferSize = 4096;
    byte[] buffer = new byte[bufferSize];
    int bytesRead = 0;

    using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, FileOptions.SequentialScan))
    {
        using (var writer = new StreamWriter(stream))
        {
            int offset = 0;
            while (offset < content.Length)
            {
                int bytesToWrite = Math.Min(bufferSize, content.Length - offset);
                var chunk = content.Substring(offset, bytesToWrite);
                writer.Write(chunk);
                writer.Flush();
                offset += bytesToWrite;
            }
        }
    }
}
```
In this case, you can see that you still have to deal with the file chunks more directly.


##Conclusion

I hope streams make more sense to you now as it does for me.

Thanks again for reading!

If you like this article:

Leave a comment (You can just say hi!)
Let's connect on Twitter [@theguspear](https://twitter.com/theguspear)

Catch you next week,

Gus.