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.