/commands/csharp/read-file-line-by-line
c# read a text file line by line (File.ReadLines vs File.ReadAllLines)
- 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
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.csOutput
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
| Method | Returns | Use 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) | string | The 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. UsePath.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
ReadLinesenumeration is in progress; finish or dispose the loop before writing the same file. - Missing namespace. In older projects without implicit usings,
Fileneedsusing 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.