CSV File Processing

Vaibhav • September 10, 2025

CSV files - short for Comma-Separated Values - are one of the most widely used formats for storing tabular data. Whether you're importing data from Excel, exporting reports, or integrating with external systems, chances are you'll encounter CSV files. In this article, we’ll explore how to read, parse, and write CSV files in C#, how to handle edge cases like quoted fields and embedded commas, and how to build robust CSV workflows using only the concepts we've covered so far.

What Is a CSV File?

A CSV file is a plain text file where each line represents a row of data, and each value in the row is separated by a comma. For example:

Name,Age,City
Vaibhav,28,Pune
Aparna,35,Mumbai

This format is simple, portable, and supported by virtually every spreadsheet and database tool. But it also comes with quirks - like handling commas inside quoted fields or newlines within cells - which we’ll address later.

Reading a CSV File Line-by-Line

The simplest way to read a CSV file is using File.ReadLines or StreamReader. Let’s start with a basic example:

foreach (string line in File.ReadLines("data.csv"))
{
    string[] fields = line.Split(',');
    Console.WriteLine($"Name: {fields[0]}, Age: {fields[1]}, City: {fields[2]}");
}

This reads each line, splits it by commas, and prints the values. It assumes that every line has exactly three fields and no embedded commas or quotes.

Split(',') works for simple CSVs, but fails when fields contain commas inside quotes. We’ll handle that later.

Skipping the Header Row

Most CSV files start with a header row that names the columns. You can skip it using a simple flag:

bool isFirstLine = true;

foreach (string line in File.ReadLines("data.csv"))
{
    if (isFirstLine)
    {
        isFirstLine = false;
        continue;
    }

    string[] fields = line.Split(',');
    Console.WriteLine($"Name: {fields[0]}, Age: {fields[1]}, City: {fields[2]}");
}

This ensures that your logic only processes actual data rows, not column headers.

Using StreamReader for More Control

For larger files or more control, use StreamReader. It lets you read line-by-line and manage resources efficiently:

using StreamReader reader = new StreamReader("data.csv");

string? line;
while ((line = reader.ReadLine()) != null)
{
    string[] fields = line.Split(',');
    Console.WriteLine($"Name: {fields[0]}, Age: {fields[1]}, City: {fields[2]}");
}

This approach is memory-efficient and works well for large datasets. You can also add logic to skip headers or validate fields.

Handling Quoted Fields and Embedded Commas

Real-world CSV files often contain fields like "New York, NY" or "Vaibhav ""The Coder""". These require special handling. Let’s write a simple parser that respects quotes:

static string[] ParseCsvLine(string line)
{
    List<string> fields = new List<string>();
    bool inQuotes = false;
    StringBuilder field = new StringBuilder();

    foreach (char c in line)
    {
        if (c == '\"')
        {
            inQuotes = !inQuotes;
        }
        else if (c == ',' && !inQuotes)
        {
            fields.Add(field.ToString());
            field.Clear();
        }
        else
        {
            field.Append(c);
        }
    }

    fields.Add(field.ToString());
    return fields.ToArray();
}

This parser walks through each character, tracks whether it’s inside quotes, and builds fields accordingly. It handles embedded commas and quoted values correctly.

Always use a proper CSV parser when working with external data. Even simple-looking files can contain edge cases that break naive Split logic.

Writing CSV Files

Writing CSV files is straightforward - just write each row as a comma-separated string. Let’s write a list of objects to a file:

List<(string Name, int Age, string City)> people = new()
{
    ("Vaibhav", 28, "Pune"),
    ("Aparna", 35, "Mumbai")
};

using StreamWriter writer = new StreamWriter("output.csv");
writer.WriteLine("Name,Age,City");

foreach (var person in people)
{
    writer.WriteLine($"{person.Name},{person.Age},{person.City}");
}

This creates a CSV file with a header and two rows. You can extend this to write any structured data.

Escaping Quotes and Commas

If a field contains a comma or quote, it must be enclosed in quotes, and quotes must be doubled:

static string EscapeCsvField(string field)
{
    if (field.Contains(',') || field.Contains('\"'))
    {
        field = field.Replace("\"", "\"\"");
        return $"\"{field}\"";
    }

    return field;
}

Use this when writing fields to ensure the output is valid and readable by other tools.

Validating CSV Data

CSV files are prone to errors - missing fields, malformed lines, or inconsistent formats. Always validate before processing:

foreach (string line in File.ReadLines("data.csv"))
{
    string[] fields = line.Split(',');

    if (fields.Length != 3)
    {
        Console.WriteLine($"Invalid line: {line}");
        continue;
    }

    Console.WriteLine($"Valid: {fields[0]}, {fields[1]}, {fields[2]}");
}

This ensures your logic only processes well-formed rows and skips problematic ones.

Working with CSV in Memory

You can load CSV data into memory for filtering, sorting, or transformation. For example:

List<(string Name, int Age, string City)> people = new();

foreach (string line in File.ReadLines("data.csv").Skip(1))
{
    string[] fields = line.Split(',');
    people.Add((fields[0], int.Parse(fields[1]), fields[2]));
}

var filtered = people.Where(p => p.City == "Pune");

foreach (var person in filtered)
{
    Console.WriteLine($"{person.Name} from Pune");
}

This loads the data, filters by city, and prints matching entries. You can extend this to group, sort, or export results.

Handling Large CSV Files

For large files, avoid loading everything into memory. Instead, process line-by-line using StreamReader:

using StreamReader reader = new StreamReader("bigdata.csv");

string? line;
while ((line = reader.ReadLine()) != null)
{
    string[] fields = line.Split(',');
    // Process fields here
}

This keeps memory usage low and scales well for millions of rows.

Real-World Use Case: Importing User Data

Let’s build a simple importer that reads a CSV file of users and prints a summary:

using StreamReader reader = new StreamReader("users.csv");

string? line;
bool isFirst = true;
int count = 0;

while ((line = reader.ReadLine()) != null)
{
    if (isFirst)
    {
        isFirst = false;
        continue;
    }

    string[] fields = line.Split(',');
    string name = fields[0];
    string email = fields[1];

    Console.WriteLine($"Imported: {name} ({email})");
    count++;
}

Console.WriteLine($"Total users imported: {count}");

This reads a file with Name,Email columns, skips the header, and prints each user. You can extend this to validate emails, store in a database, or send welcome messages.

Summary

CSV files are a simple yet powerful way to exchange tabular data. In this article, you learned how to read and write CSV files using File.ReadLines, StreamReader, and StreamWriter. You built a custom parser to handle quoted fields, validated input, escaped output, and processed data in memory and on disk. These skills are essential for integrating with external systems, importing/exporting data, and building robust data pipelines. In the next article, we’ll explore configuration files - how to store and load settings using formats like INI, JSON, and XML.