Table of Contents
Back to Blog

C# Deep Dive

How to Count Lines in a File Using C# (And the Int32 Overflow Trap Nobody Warns You About)

Count lines in a file using C# — File.ReadAllLines, File.ReadLines, StreamReader, and async methods. Includes the Int32 overflow trap, GC pressure benchmarks, and .NET 6+ best practices.

.NET 6+.NET 8ASP.NET Core
Published: May 13, 2026Updated: May 13, 202615 min readAuthor: Line Counter Editorial Team
C#.NETFile I/OPerformanceAzure

Three ways to count lines in C#:

  • One loads the entire file into memory.
  • One streams the file but can overflow at 2.1 billion lines.
  • One is the right default for production.

Here is the decision in one table:

MethodMemoryLarge filesMain risk
File.ReadAllLines().LengthhighnoOOM
File.ReadLines().Count()lowriskyInt32 overflow
File.ReadLines().LongCount()lowbetterone string per line
StreamReader looplowyesmore code
Span<byte> byte scanlowestyesraw byte semantics

This guide covers c# count lines methods from simple one-liners to .NET 6+ byte scanning. It also explains the file readlines count overflow trap: Count() returns int, so a huge log can throw even when streaming memory use is low. If your search was "c# count lines in file," the short answer is streaming plus LongCount().

If you only need a quick result without a .NET project, use the Line Counter tool. If you are writing C# code for logs, CSVs, uploads, or Azure services, keep reading.

Quick Method Guide

ScenarioUse this
Small known fileread-all method with .Length
Concise streaming countstreaming method with .LongCount()
Production large fileStreamReader loop with long counter
ASP.NET Coreasync StreamReader.ReadLineAsync(...)
Maximum throughputSpan<byte> or pooled byte buffer scan
More than 2.1 billion lines possiblenever use LINQ Count()

For most count lines c# code, start with the streaming method plus LongCount(). For c# count lines large file work in services, use a StreamReader count lines loop so you control buffers, async behavior, cancellation, and the counter type.

Method 1: File.ReadAllLines - Simple but Memory-Hungry

The shortest beginner answer is:

using System.IO;

int lineCount = File.ReadAllLines("data.txt").Length;

With basic validation:

using System.IO;

public static int CountLinesSmallFile(string filePath)
{
    if (!File.Exists(filePath))
        throw new FileNotFoundException($"File not found: {filePath}");

    return File.ReadAllLines(filePath).Length;
}

Microsoft documents File.ReadAllLines as opening a file, reading all lines into a string array, and closing the file. That array is the memory trap.

File sizeUse File.ReadAllLines?Reason
Under 50MBusually finesimple and readable
50MB to 500MBbe carefulall lines are materialized
Over 500MBavoidOOM and GC pressure risk

The read-all method is useful for tests, small config files, and quick local tools. It is not a safe general answer for c# count lines large file scenarios.

Method 2: File.ReadLines - Streaming with an Overflow Trap

Most Stack Overflow-style answers look like this:

using System.IO;
using System.Linq;

int lineCount = File.ReadLines("data.txt").Count();

The streaming method is better than the read-all method because it does not materialize the entire file first. Microsoft documents it as returning an enumerable collection whose enumeration can begin before the whole collection is returned.

string[] allLines = File.ReadAllLines("data.txt");
IEnumerable<string> lazyLines = File.ReadLines("data.txt");

The streaming part is good. The Count() part is the trap.

The Int32 overflow trap

LINQ Count() returns int.

public static int Count<TSource>(this IEnumerable<TSource> source)

If the sequence has more than Int32.MaxValue items, Count() throws OverflowException. That means file readlines count overflow can happen even though the file is streamed and memory stays low.

// Dangerous on files with more than 2,147,483,647 lines
int count = File.ReadLines("huge.log").Count();

The fix is one word:

using System.IO;
using System.Linq;

long lineCount = File.ReadLines("huge.log").LongCount();

LongCount() returns long, so it is the safe concise method.

public static long CountLinesStreaming(string filePath)
{
    if (!File.Exists(filePath))
        throw new FileNotFoundException($"File not found: {filePath}");

    return File.ReadLines(filePath).LongCount();
}

Use LongCount c# style whenever the file is not tightly bounded. The cost is effectively the same as Count(); the return type is the difference that matters.

Streaming still allocates strings

The streaming method reads lazily, but every line is still a string. For millions or billions of rows, that means many short-lived string allocations and Gen0 collections.

That is acceptable for many business files. It is not the fastest possible way to count newlines.

Use streaming plus LongCount() when:

  • you want simple c# count lines code
  • the file might be large but not extreme
  • you need text decoding and line semantics
  • you do not need async cancellation

Move to StreamReader when this runs in production hot paths.

Method 3: StreamReader Manual Loop - The Production Choice

A manual StreamReader count lines loop avoids Int32 overflow because you choose long.

using System.IO;

public static long CountLinesWithStreamReader(string filePath)
{
    long count = 0;

    using var reader = new StreamReader(filePath);
    while (reader.ReadLine() != null)
    {
        count++;
    }

    return count;
}

This is the baseline production method.

Add an explicit buffer and SequentialScan

Default stream buffers are deliberately conservative. In .NET source, StreamReader has a small internal character buffer, and FileStream defaults have historically been small enough that an explicit large buffer can help large sequential reads.

Use an explicit buffer for c# count lines large file code:

using System.IO;

public static long CountLinesOptimized(string filePath)
{
    const int bufferSize = 1024 * 1024;
    long count = 0;

    using var fileStream = new FileStream(
        filePath,
        FileMode.Open,
        FileAccess.Read,
        FileShare.Read,
        bufferSize,
        FileOptions.SequentialScan);

    using var reader = new StreamReader(
        fileStream,
        bufferSize: bufferSize);

    while (reader.ReadLine() != null)
        count++;

    return count;
}

FileOptions.SequentialScan tells the operating system that the file will be read from beginning to end. That can improve read-ahead behavior and cache decisions.

This StreamReader count lines version is a strong default when:

  • files can be hundreds of MB or GB
  • you need a long count
  • you need predictable memory use
  • you want text decoding and normal line-ending handling

For related JVM patterns, see Java line counting.

Async version for ASP.NET Core

Synchronous file I/O can block ASP.NET Core request threads. For c# async count lines in web apps, use async file I/O and pass the request cancellation token.

using System.IO;
using System.Threading;
using System.Threading.Tasks;

public static async Task<long> CountLinesAsync(
    string filePath,
    CancellationToken cancellationToken = default)
{
    const int bufferSize = 1024 * 1024;

    await using var fileStream = new FileStream(
        filePath,
        FileMode.Open,
        FileAccess.Read,
        FileShare.Read,
        bufferSize,
        FileOptions.Asynchronous | FileOptions.SequentialScan);

    using var reader = new StreamReader(
        fileStream,
        bufferSize: bufferSize);

    long count = 0;
    while (await reader.ReadLineAsync(cancellationToken) != null)
    {
        count++;
    }

    return count;
}

ASP.NET Core example:

[HttpGet("line-count")]
public async Task<IActionResult> GetLineCount(string filename)
{
    var count = await CountLinesAsync(filename, HttpContext.RequestAborted);
    return Ok(new { lines = count });
}

Microsoft documents the ReadLineAsync(CancellationToken) overload as returning ValueTask<string?> and returning null at end of stream. Use it on .NET 6+ code paths when cancellation matters.

Method 4: Span<T> Byte Scanning - Zero String Allocation

If you only need physical newline counts, scan bytes.

This avoids string allocation entirely.

using System;
using System.IO;

public static long CountLinesFast(string filePath)
{
    const int bufferSize = 64 * 1024;
    Span<byte> buffer = stackalloc byte[bufferSize];

    long count = 0;
    bool sawAnyByte = false;
    byte lastByte = (byte)'\n';

    using var fileStream = new FileStream(
        filePath,
        FileMode.Open,
        FileAccess.Read,
        FileShare.Read,
        bufferSize,
        FileOptions.SequentialScan);

    int bytesRead;
    while ((bytesRead = fileStream.Read(buffer)) > 0)
    {
        sawAnyByte = true;
        var chunk = buffer[..bytesRead];

#if NET8_0_OR_GREATER
        count += chunk.Count((byte)'\n');
#else
        count += CountNewlines(chunk);
#endif

        lastByte = chunk[^1];
    }

    if (sawAnyByte && lastByte != (byte)'\n')
        count++;

    return count;
}

private static int CountNewlines(ReadOnlySpan<byte> span)
{
    int count = 0;
    foreach (var b in span)
    {
        if (b == (byte)'\n')
            count++;
    }
    return count;
}

This is the Span<T> count lines .NET method for high-throughput tools. It is fastest because it avoids:

  • string allocation
  • text decoding
  • ReadLine() logic
  • LINQ enumeration overhead

Stack allocation is fast, but keep it modest. A 64KB stackalloc buffer is a reasonable upper bound in many CLI tools. For library code or unknown hosting environments, prefer ArrayPool<byte> so you do not consume too much stack.

Pooled variant:

using System.Buffers;
using System.IO;

public static long CountLinesFastPooled(string filePath)
{
    const int bufferSize = 1024 * 1024;
    byte[] rented = ArrayPool<byte>.Shared.Rent(bufferSize);

    try
    {
        var buffer = rented.AsSpan(0, bufferSize);
        long count = 0;
        bool sawAnyByte = false;
        byte lastByte = (byte)'\n';

        using var stream = new FileStream(
            filePath,
            FileMode.Open,
            FileAccess.Read,
            FileShare.Read,
            bufferSize,
            FileOptions.SequentialScan);

        int bytesRead;
        while ((bytesRead = stream.Read(buffer)) > 0)
        {
            sawAnyByte = true;
            var chunk = buffer[..bytesRead];
            count += CountNewlines(chunk);
            lastByte = chunk[^1];
        }

        if (sawAnyByte && lastByte != (byte)'\n')
            count++;

        return count;
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(rented);
    }
}

Use byte scanning when invalid text encoding should not matter and line breaks are all you need.

Benchmark: All Methods Compared

BenchmarkDotNet shape:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.IO;
using System.Linq;

[MemoryDiagnoser]
public class LineCountBenchmarks
{
    private const string FilePath = "testdata/large.txt";

    [Benchmark(Baseline = true)]
    public int ReadAllLines()
        => File.ReadAllLines(FilePath).Length;

    [Benchmark]
    public long ReadLinesLongCount()
        => File.ReadLines(FilePath).LongCount();

    [Benchmark]
    public long StreamReaderDefault()
        => CountLinesWithStreamReader(FilePath);

    [Benchmark]
    public long StreamReaderLargeBuffer()
        => CountLinesOptimized(FilePath);

    [Benchmark]
    public long SpanByteScan()
        => CountLinesFast(FilePath);
}

BenchmarkRunner.Run<LineCountBenchmarks>();

Directional results for a 500MB newline-delimited file on SSD storage, .NET 8:

MethodTime shapeAllocationMain issue
File.ReadAllLines().Lengthslowvery highreads whole file
File.ReadLines().Count()mediumlow live memoryfile readlines count overflow
File.ReadLines().LongCount()mediumlow live memorystring per line
StreamReader defaultmediumlowconservative buffers
StreamReader large bufferfasterlowmore code
Span<T> byte scanfastestnear zerobyte semantics

The exact numbers depend on storage, CPU, line length, encoding, OS file cache, and .NET version. The stable ranking is what matters:

  • File.ReadAllLines is simplest and most memory-hungry.
  • LongCount c# fixes the Count() overflow problem.
  • StreamReader count lines loops are the production default.
  • Span<T> count lines .NET byte scans are fastest when text decoding is unnecessary.

.NET Version Compatibility

Method.NET Framework.NET Core.NET 5.NET 6+
File.ReadAllLinesyesyesyesyes
File.ReadLines4.0+yesyesyes
Enumerable.LongCount3.5+yesyesyes
StreamReaderyesyesyesyes
ReadLineAsync(CancellationToken)nononoyes
Span<T> byte APIsno2.1+yesyes
FileOptions.SequentialScanyesyesyesyes

If you target .NET Framework 4.8, use synchronous StreamReader or File.ReadLines().LongCount(). If you target .NET 6+, use c# async count lines methods in ASP.NET Core and byte scanning for high-throughput CLI tools.

A Production-Ready Line Counter Class

using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public static class LineCounter
{
    private const long SmallFileThreshold = 50L * 1024 * 1024;
    private const int BufferSize = 1024 * 1024;

    public static long Count(string filePath)
    {
        var fileInfo = new FileInfo(filePath);
        if (!fileInfo.Exists)
            throw new FileNotFoundException($"File not found: {filePath}");

        if (fileInfo.Length < SmallFileThreshold)
            return File.ReadLines(filePath).LongCount();

        return CountWithStreamReader(filePath);
    }

    public static async Task<long> CountAsync(
        string filePath,
        CancellationToken cancellationToken = default)
    {
        if (!File.Exists(filePath))
            throw new FileNotFoundException($"File not found: {filePath}");

        await using var fileStream = new FileStream(
            filePath,
            FileMode.Open,
            FileAccess.Read,
            FileShare.Read,
            BufferSize,
            FileOptions.Asynchronous | FileOptions.SequentialScan);

        using var reader = new StreamReader(fileStream, bufferSize: BufferSize);

        long count = 0;
        while (await reader.ReadLineAsync(cancellationToken) != null)
            count++;

        return count;
    }

    public static long CountNonEmpty(string filePath)
        => File.ReadLines(filePath)
               .LongCount(line => !string.IsNullOrWhiteSpace(line));

    private static long CountWithStreamReader(string filePath)
    {
        long count = 0;

        using var fileStream = new FileStream(
            filePath,
            FileMode.Open,
            FileAccess.Read,
            FileShare.Read,
            BufferSize,
            FileOptions.SequentialScan);

        using var reader = new StreamReader(fileStream, bufferSize: BufferSize);

        while (reader.ReadLine() != null)
            count++;

        return count;
    }
}

Usage:

var count = LineCounter.Count("data.txt");
var countAsync = await LineCounter.CountAsync("data.txt", cancellationToken);
var nonEmpty = LineCounter.CountNonEmpty("config.txt");

Production checklist:

  • Use LongCount() instead of Count() for unbounded files.
  • Use StreamReader, not File.ReadAllLines, for large files.
  • Set explicit buffers for large sequential reads.
  • Add FileOptions.SequentialScan.
  • Use async file I/O in ASP.NET Core.
  • Pass CancellationToken.
  • Dispose streams with using or await using.
  • Decide whether byte semantics or text semantics are required.

Special Scenarios

stdin

using System;
using System.IO;

long count = 0;
using var reader = new StreamReader(Console.OpenStandardInput());

while (reader.ReadLine() != null)
    count++;

Console.WriteLine(count);

strings

Use StringReader when you want the same line behavior as ReadLine():

using System.IO;

public static long CountLinesInString(string text)
{
    if (string.IsNullOrEmpty(text))
        return 0;

    long count = 0;
    using var reader = new StringReader(text);

    while (reader.ReadLine() != null)
        count++;

    return count;
}

Raw newline count:

public static long CountLinesInStringFast(string text)
{
    if (string.IsNullOrEmpty(text))
        return 0;

    long count = 0;
    foreach (char c in text)
    {
        if (c == '\n')
            count++;
    }

    if (text[^1] != '\n')
        count++;

    return count;
}

Windows line endings

StreamReader.ReadLine() and File.ReadLines return strings without the terminating carriage return or line feed. Windows CRLF files need no special handling.

In byte scanning, counting \n is still correct for CRLF because each CRLF line ending includes one LF byte. Lone old-Mac \r files need separate handling if they matter to your domain.

Which C# Method Should You Use?

Use this c# count lines decision tree when you need the fastest safe choice:

Need to count lines in C#?
|
+-- Small known file?
|   +-- streaming method with LongCount()
|
+-- Large text file?
|   +-- StreamReader + explicit buffer + SequentialScan
|
+-- ASP.NET Core request path?
|   +-- StreamReader.ReadLineAsync(CancellationToken)
|
+-- Raw speed and newline bytes only?
|   +-- Span<byte> byte scan or ArrayPool<byte>
|
+-- More than 2.1 billion lines possible?
    +-- LongCount or manual long counter, never Count()

The safest short answer is the streaming method with LongCount(). The safest production answer is a StreamReader count lines loop. The fastest answer is byte scanning.

FAQ

How do I count lines in a file in C#?

Use the streaming method with LongCount() for a concise count. Use StreamReader with a long counter for production code.

What is the difference between ReadAllLines and ReadLines?

The read-all method reads every line into a string[]. The streaming method returns a lazy IEnumerable<string> and reads as you enumerate.

Why does File.ReadLines().Count() throw OverflowException?

LINQ Count() returns int. If the sequence has more than Int32.MaxValue elements, it throws. Use LongCount() or a manual long counter to fix file readlines count overflow.

How do I count lines in a large file in C#?

Use a StreamReader count lines loop with an explicit buffer and FileOptions.SequentialScan, or use byte scanning when text decoding is unnecessary.

How do I count lines asynchronously in C#?

Use StreamReader.ReadLineAsync(CancellationToken) on .NET 6+ with a FileStream opened using FileOptions.Asynchronous.

What is the fastest way to count lines in C#?

For raw newline counts, Span<T> count lines .NET byte scanning is usually fastest because it avoids string allocation. This is the performance path for c# count lines workloads where decoding is unnecessary.

How do I count non-empty lines in C#?

Use:

long count = File.ReadLines(path)
    .LongCount(line => !string.IsNullOrWhiteSpace(line));

How do I count lines in a string in C#?

Use StringReader for text-line semantics, or count \n characters directly if raw newline behavior is acceptable.

Sources Checked

Need a Quick Line Count Without a .NET Project?

Paste your file into the Line Counter. It works on logs, CSVs, configs, and plain text files. No OverflowException, no OOM, just the number.

Frequently Asked Questions

How do I count lines in a file in C#?

Use the streaming method with LongCount for a concise count, or a StreamReader loop with a long counter for production code.

What is the difference between ReadAllLines and ReadLines?

The read-all method returns a string array. The streaming method returns IEnumerable<string> and reads lazily as you enumerate.

Why does File.ReadLines().Count() throw OverflowException?

LINQ Count returns Int32. If the sequence has more than 2,147,483,647 items, it throws OverflowException. Use LongCount or a manual long counter.

How do I count lines in a large file in C#?

Use StreamReader with an explicit buffer and FileOptions.SequentialScan, or use a byte-scanning method when you only need newline counts.

How do I count lines asynchronously in C#?

Use StreamReader.ReadLineAsync(CancellationToken) on .NET 6+ with a FileStream opened with FileOptions.Asynchronous.

What is the fastest way to count lines in C#?

A byte scan over a Span<byte> or pooled byte buffer is usually fastest because it avoids creating one string per line.

How do I count non-empty lines in C#?

Use File.ReadLines(path).LongCount(line => !string.IsNullOrWhiteSpace(line)).

How do I count lines in a string in C#?

Use StringReader for text-line semantics or count '\n' characters directly when raw newline behavior is enough.

Related Guides