Skip to content

/commands/csharp/read-file-line-by-line

c# read a text file line by line (File.ReadLines vs File.ReadAllLines)

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · 10.0.401updated
tool
C#
versions
.NET Framework 4.0+ (File.ReadLines), .NET 10
os
any

foreach (var line in File.ReadLines("data.txt")) reads a text file one line at a time: it returns a lazy sequence, so only the current line is in memory and you can break early. File.ReadAllLines(path) returns all lines as a string[] at once, which is simpler for small files. Both remove the line terminators (\n, \r\n or \r) and read UTF-8 by default.

Examples

bash
printf 'id,name\r\n1,Ada\r\n2,Linus\n\n3,Grace' > data.csv
cat > read.cs <<'EOF'
// Lazy: one line in memory at a time, works for huge files
foreach (var (line, i) in File.ReadLines("data.csv").Select((l, i) => (l, i)))
    Console.WriteLine($"{i + 1}: [{line}]");

// LINQ on the lazy sequence: skip the header and blank lines
var names = File.ReadLines("data.csv")
    .Skip(1)
    .Where(l => l.Length > 0)
    .Select(l => l.Split(',')[1]);
Console.WriteLine(string.Join(" | ", names));

// Everything at once
string[] all = File.ReadAllLines("data.csv");
Console.WriteLine($"{all.Length} lines");

// StreamReader for full control
using var reader = new StreamReader("data.csv");
Console.WriteLine($"header: {reader.ReadLine()}");

try { File.ReadLines("missing.csv").First(); }
catch (FileNotFoundException e) { Console.WriteLine(e.GetType().Name); }
EOF
dotnet run read.cs

Output

1: [id,name]
2: [1,Ada]
3: [2,Linus]
4: []
5: [3,Grace]
Ada | Linus | Grace
5 lines
header: id,name
FileNotFoundException

Windows (\r\n) and Unix (\n) line endings are both stripped, the last line is returned without a trailing newline, and the empty line comes back as an empty string. File and StreamReader live in System.IO, which is imported implicitly in .NET 6+ projects and file-based apps.

Methods compared

MethodReturnsUse when
File.ReadLines(path)IEnumerable<string> (lazy)Large files, early exit, LINQ pipelines
File.ReadAllLines(path)string[]Small files when you need Length or index access
File.ReadAllText(path)stringThe whole file as one string
new StreamReader(path).ReadLine()string? (null at end)Mixed reading, custom buffer size, or reading a stream
File.ReadLinesAsync(path)IAsyncEnumerable<string>Async code, with await foreach (.NET 7+)
File.ReadLines(path, Encoding.Latin1)IEnumerable<string>Files that are not UTF-8

Pitfalls

  • Relative paths are resolved against the current working directory, which in Visual Studio is usually bin/Debug/netX/, not the project folder. Use Path.Combine(AppContext.BaseDirectory, "data.csv") or copy the file to the output.
  • Enumerating twice reads the file twice with ReadLines; call .ToList() once if you need several passes.
  • The file stays open while a ReadLines enumeration is in progress; finish or dispose the loop before writing the same file.
  • Missing namespace. In older projects without implicit usings, File needs using System.IO;, otherwise the build fails with CS0103 (The name 'File' does not exist).

Confirm it worked

Print each line with its number and brackets around it, as in the example: missing lines, stray \r characters or unexpected blank lines become visible at once.

FAQ

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

ReadLines returns a lazy IEnumerable<string> that reads one line at a time while you iterate, so memory stays small and you can stop early. ReadAllLines reads the whole file into a string[] before returning.

Which encoding is used?

UTF-8 by default, with detection of a byte order mark (UTF-8, UTF-16, UTF-32). Pass an Encoding as the second argument for other files, for example Encoding.Latin1.

How do I read a file asynchronously?

Use File.ReadLinesAsync (.NET 7+) with await foreach, or StreamReader.ReadLineAsync in a loop. Both avoid blocking a thread while waiting for the disk.

Sources