skip to content
Khalid Abuhakmeh logo Khalid Abuhakmeh

C# Read File: ReadAllText, ReadAllBytes, and More

How to read a file in C# with File.ReadAllText, File.ReadAllBytes, File.ReadAllLines, and StreamReader, with benchmarks on memory and speed for each.

9 min read Updated

To read a file in C#, the System.IO.File class gives you several static methods: File.ReadAllText for the whole file as a string, File.ReadAllBytes for raw bytes, File.ReadAllLines for an array of lines, or File.ReadLines/StreamReader if you want to stream through a large file without loading it all into memory at once. There are almost too many options, and for .NET coding newbies, it may be confusing which option to choose.

Updated October 2026 for modern .NET (.NET 8/9), including the async methods and refreshed benchmarks.

Here’s the quick decision guide:

You want to… Use Notes
The whole file as a string File.ReadAllText Simplest; loads everything into memory
Raw bytes (images, binary formats) File.ReadAllBytes Returns a byte[]; 2 GB limit
Every line as a string[] File.ReadAllLines Convenient, but buffers the whole file
Stream line-by-line without buffering File.ReadLines or StreamReader Most memory-friendly for large files
Any of the above without blocking File.ReadAllTextAsync, ReadAllBytesAsync, ReadAllLinesAsync Use in async code and web apps

In this post, we’ll explore the many ways we can read a file and also benchmark each approach to understand what exactly it is doing.

Using System.IO.File

The .NET base class library provides a static class called File under the System.IO namespace. This class is what we can use to open, read, create, and update any file on disk. That said, we have many methods we can use to read a file:

  • Open & ReadLine
  • ReadLines
  • ReadAllText
  • ReadAllBytes
  • ReadAllLines
  • the async variants: ReadAllTextAsync, ReadAllBytesAsync, ReadAllLinesAsync, and ReadLinesAsync

We’ll explore each of these methods, their performance characteristics, and when we may want to use each one.

Read a Text File Line by Line in C#

If what you need is to read a file line by line, or read all lines from a file, the next few sections cover both: a controlled, one-line-at-a-time approach with StreamReader/ReadLine, and the convenience methods File.ReadLines and File.ReadAllLines that read a text file line by line for you.

Open & StreamReader Readline

The first approach is the most tedious of our strategies but gives us more control over reading the contents of a file.

public void ReadLine()
{
using var stream = System.IO.File.Open(File, FileMode.Open);
using var reader = new StreamReader(stream);
var line = reader.ReadLine();
}

The first step is to open the file and retrieve a Stream. In this case, we will be dealing with a FileStream. Next, we use a StreamReader to read the bytes coming from our FileStream. Finally, we can call ReadLine to get the first line of our file. The approach of using a stream reader gives us control of when to stop reading our file. The controlled approach helps reduce CPU cycles and consumption of memory.

ReadLines

The next approach to reading a file is by using File.ReadLines to create an iterator pointing to our file’s data.

public void ReadLines()
{
var file = System.IO.File.ReadLines(File);
}

If we decompile the ReadLines method, we can see what is happening inside .NET. The first stop in our expedition leads us to the following method.

public static IEnumerable<string> ReadLines(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
if (path.Length == 0)
throw new ArgumentException(SR.Argument_EmptyPath, nameof(path));
return ReadLinesIterator.CreateIterator(path, Encoding.UTF8);
}

There is an internal class called ReadLinesIterator that returns an iterator. When we follow the CreateIterator call to the class definition, we see the following relevant code.

public override bool MoveNext()
{
if (this._reader != null)
{
this.current = _reader.ReadLine();
if (this.current != null)
return true;
// To maintain 4.0 behavior we Dispose
// after reading to the end of the reader.
Dispose();
}
return false;
}

It’s our friend, ReadLine! Note that the iterator only starts to read from the file when we begin to process the iterator. Given our code above, we should not expect any CPU or additional memory utilization until we call the iterator in a loop of some kind.

This approach to reading files can reduce the noise in our previous code, but has the same benefits as the ReadLine approach because it is the ReadLine approach.

ReadAllText

The ReadAllText method is for us folks who don’t have time to fiddle around with streams and want the entire contents of a file NOW. The documentation says as much.

Opens a text file, reads all the text in the file, and then closes the file. .NET docs

The resulting value is a string of the entire contents of a file. As we may have guessed, this can cause some memory pressure when dealing with larger files. Here is the internal implementation of ReadAllText.

private static string InternalReadAllText(string path, Encoding encoding)
{
Debug.Assert(path != null);
Debug.Assert(encoding != null);
Debug.Assert(path.Length > 0);
using (StreamReader sr = new StreamReader(path, encoding, detectEncodingFromByteOrderMarks: true))
return sr.ReadToEnd();
}

As we can see, a call to StreamReader.ReadToEnd is made, which returns the entire contents of the file.

ReadAllBytes

Not all files will contain string-friendly values. In some cases, we may be dealing with a proprietary binary format. That is where our next method is helpful. The ReadAllBytes method, like ReadAllText, will take all the binary elements of a file and read them into memory.

public void ReadAllBytes()
{
byte[] bytes = System.IO.File.ReadAllBytes(File);
}

What occurs inside .NET is interesting.

public static byte[] ReadAllBytes(string path)
{
// bufferSize == 1 used to avoid unnecessary buffer in FileStream
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1))
{
long fileLength = fs.Length;
if (fileLength > int.MaxValue)
{
throw new IOException(SR.IO_FileTooLong2GB);
}
else if (fileLength == 0)
{
#if !MS_IO_REDIST
// Some file systems (e.g. procfs on Linux) return 0 for length even when there's content.
// Thus we need to assume 0 doesn't mean empty.
return ReadAllBytesUnknownLength(fs);
#endif
}
int index = 0;
int count = (int)fileLength;
byte[] bytes = new byte[count];
while (count > 0)
{
int n = fs.Read(bytes, index, count);
if (n == 0)
throw Error.GetEndOfFile();
index += n;
count -= n;
}
return bytes;
}
}

The implementation for ReadAllBytes uses the Read method of StreamReader to read all the bytes into memory. Note the two-gigabyte limit as well.

ReadAllLines

Similar to ReadAllText, ReadAllLines will read each line and return the file in the form of an array. This method can be helpful for parsing log files or filtering data file formats like a comma-separated value.

public void ReadAllLines()
{
string[] lines = System.IO.File.ReadAllLines(File);
}

The implementation of ReadAllLines utilizes ReadLine.

private static string[] InternalReadAllLines(string path, Encoding encoding)
{
Debug.Assert(path != null);
Debug.Assert(encoding != null);
Debug.Assert(path.Length != 0);
string line;
List<string> lines = new List<string>();
using (StreamReader sr = new StreamReader(path, encoding))
while ((line = sr.ReadLine()) != null)
lines.Add(line);
return lines.ToArray();
}

The downside to this approach, is it can be expensive in terms of memory. Buffering the entire contents of a file in memory can lead to memory exhaustion, not to mention expensive garbage collection calls.

Reading a File Asynchronously in C#

Every ReadAll* method has an async counterpart that returns a Task, so you can read a file without blocking the calling thread. These are the methods you want inside web apps, desktop UI event handlers, or any async method where blocking on disk I/O would waste a thread.

using System.IO;
// whole file as a string
string text = await File.ReadAllTextAsync("example.txt");
// raw bytes
byte[] bytes = await File.ReadAllBytesAsync("example.txt");
// every line as an array
string[] lines = await File.ReadAllLinesAsync("example.txt");

If you want to stream a large file line by line without buffering it all into memory, .NET also provides File.ReadLinesAsync, which returns an IAsyncEnumerable<string> you can walk with await foreach:

await foreach (string line in File.ReadLinesAsync("example.txt"))
{
Console.WriteLine(line);
}

Pass a CancellationToken to any of these (for example, File.ReadAllTextAsync(path, cancellationToken)) when you need to honor timeouts or user cancellation. As a rule of thumb: use the async methods in async code, and reach for ReadLinesAsync when the file is large enough that you’d rather not hold it all in memory at once.

Benchmarks

Let’s run each of our read methods through a benchmark test using BenchmarkDotNet.

using System.IO;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
namespace ReadingRainbow
{
class Program
{
static void Main(string[] args)
{
BenchmarkRunner
.Run<Reading>();
}
}
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net90)]
public class Reading
{
private const string File = "example.txt";
[Benchmark]
public void ReadLine()
{
using var stream = System.IO.File.Open(File, FileMode.Open);
using var reader = new StreamReader(stream);
var line = reader.ReadLine();
}
[Benchmark]
public void ReadLines()
{
var file = System.IO.File.ReadLines(File);
}
[Benchmark]
public void ReadAllText()
{
var text = System.IO.File.ReadAllText(File);
}
[Benchmark]
public void ReadAllBytes()
{
var bytes = System.IO.File.ReadAllBytes(File);
}
[Benchmark]
public void ReadAllLines()
{
string[] lines = System.IO.File.ReadAllLines(File);
}
}
}

The results are fascinating. (These numbers were captured on an earlier runtime; absolute timings have improved on modern .NET, but the relative memory story below still holds.)

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ReadLine 65.01 us 1.161 us 1.029 us 4.1504 - - 8.64 KB
ReadLines 50.99 us 0.759 us 0.673 us 2.2583 0.7324 - 4.63 KB
ReadAllText 62.87 us 0.780 us 0.729 us 18.1274 - - 37.27 KB
ReadAllBytes 53.13 us 1.023 us 1.137 us 3.6621 - - 7.54 KB
ReadAllLines 77.94 us 0.971 us 0.861 us 15.3809 - - 31.59 KB

We can see the ReadAll* methods take up the most memory. The benchmarks that convert the bytes of the document into strings take up the most memory, while the ReadAllBytes benchmark takes up the same bytes as the file itself. Ultimately the most memory conscious approach is ReadLines, but that’s because we never iterated over the result.

Frequently Asked Questions

How do I read a whole text file into a string in C#?

Call File.ReadAllText("path.txt"). It opens the file, reads all the text into a single string, and closes the file for you. For async code, use await File.ReadAllTextAsync("path.txt").

What is the difference between File.ReadAllLines and File.ReadLines?

File.ReadAllLines reads every line into a string[] up front, buffering the entire file in memory. File.ReadLines returns an IEnumerable<string> that reads lines lazily as you iterate, so it uses far less memory for large files. Use ReadAllLines when you need the whole array at once; use ReadLines when you’re streaming through a big file.

How do I read a file without loading it all into memory?

Use File.ReadLines (or a StreamReader with ReadLine) and process one line at a time inside a loop. Because the lines are read lazily, memory stays flat regardless of file size. The async equivalent is File.ReadLinesAsync with await foreach.

How do I read a binary file in C#?

Use File.ReadAllBytes("path"), which returns a byte[] containing the raw contents, ideal for images or proprietary formats. Note the 2 GB limit; for larger files, read from a FileStream in chunks. The async version is File.ReadAllBytesAsync.

Which file-reading method is the most memory efficient?

Streaming approaches like File.ReadLines and StreamReader are the most memory efficient because they never hold the whole file in memory. The ReadAll* methods are the most convenient but allocate memory proportional to the file size.

Conclusion

We’ve gone through all the methods on System.IO.File that can read from disk. What’s common between them all is the use of StreamReader. They are helpful to use in our applications, but all behave slightly differently. For those concerned about getting their work done quickly, the ReadAll* methods get the job done. Folks with more critical memory constraints may want to stick with using StreamReader directly.

Looking for the other direction? There’s a companion guide on how to write to a file in C# with File.WriteAllText, AppendAllText, and StreamWriter coming soon.

For more everyday syntax in one place, see my C# cheat sheet.

I hope you found this post interesting, and please leave a comment.

Comments

Related Posts