ZetCode

C# CSV - read and write CSV data

last modified October 5, 2026

C# CSV tutorial shows how to read and write comma-separated values in C# with .NET and the CsvHelper library.

CSV

CSV (Comma Separated Values) is a very popular import and export data format used in spreadsheets and databases. Each line in a CSV file is a data record. Each record consists of one or more fields, separated by commas. Fields that contain commas, quotation marks, or line breaks must be quoted and quotation marks inside quoted fields are doubled. In practice, CSV files may also use different delimiters or encodings.

In this article, we read and write CSV data with the CsvHelper library.

$ dotnet add package CsvHelper

We add the CsvHelper package to the project.

C# CSV read data by records

In the following example, we read a CSV file by records.

users.csv
FirstName,LastName,Occupation
John,Doe,gardener
Roger,Roe,driver
Lucy,Smith,teacher

We have this users.csv file.

Program.cs
using System.Globalization;
using CsvHelper;
using CsvHelper.Configuration;

var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    HasHeaderRecord = false
};

using var streamReader = File.OpenText("users.csv");
using var csvReader = new CsvReader(streamReader, csvConfig);

string value;

while (csvReader.Read())
{
    for (int i = 0; csvReader.TryGetField<string>(i, out value); i++)
    {
        Console.Write($"{value} ");
    }

    Console.WriteLine();
}

The Read method advances the reader to the next record. We read the fields of the record with TryGetField.

$ dotnet run
FirstName LastName Occupation 
John Doe gardener 
Roger Roe driver 
Lucy Smith teacher 

C# CSV read data into objects

In the next example, we read the data into objects with GetRecords.

Program.cs
using System.Globalization;
using CsvHelper;

using var streamReader = File.OpenText("users.csv");
using var csvReader = new CsvReader(streamReader, CultureInfo.InvariantCulture);

var users = csvReader.GetRecords<User>();

foreach (var user in users)
{
    Console.WriteLine(user);
}

record User(string FirstName, string LastName, string Occupation);

In the example, we define the User class and read the records of the users.csv file into instances of this class. The GetRecords returns the IEnumerable of the given type.

$ dotnet run
User { FirstName = John, LastName = Doe, Occupation = gardener }
User { FirstName = Roger, LastName = Roe, Occupation = driver }
User { FirstName = Lucy, LastName = Smith, Occupation = teacher }

C# CSV configuration

In the following example, we have a CSV file with semicolon separators and a comment. To parse this variation, we configure the parser.

users.csv
# this is users.csv file

John;Doe;gardener
Roger;Roe;driver
Lucy;Smith;teacher

We have this users.csv file.

Program.cs
using System.Globalization;
using CsvHelper;
using CsvHelper.Configuration; 

var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    HasHeaderRecord = false,
    Comment = '#',
    AllowComments = true,
    Delimiter = ";",
};

using var streamReader = File.OpenText("users.csv");
using var csvReader = new CsvReader(streamReader, csvConfig);

while (csvReader.Read())
{
    var firstName = csvReader.GetField(0);
    var lastName = csvReader.GetField(1);
    var occupation = csvReader.GetField(2);

    Console.WriteLine($"{firstName} {lastName} is {occupation}");
}

The reader is configured with CsvConfiguration.

var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    HasHeaderRecord = false,
    Comment = '#',
    AllowComments = true,
    Delimiter = ";",
};

We tell the reader that there is no header, allow comments, and use a semicolon as the delimiter. The default comment character is #; it is set explicitly here for clarity. Blank lines are ignored by default.

using var csvReader = new CsvReader(streamReader, csvConfig);

The configuration file is passed to the CsvReader.

$ dotnet run
John Doe is gardener
Roger Roe is driver
Lucy Smith is teacher

C# CSV quote fields

In the following example, we show how to quote fields.

Program.cs
using System.Globalization;
using CsvHelper;
using CsvHelper.Configuration;

var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    ShouldQuote = args => args.Field?.Contains(' ') == true
};

var users = new List<User> 
{
    new (1, "John Doe", "gardener", "1997-12-05"),
    new (2, "Lucy Smith", "teacher", "1983-05-12"),
    new (3, "Roger Roe", "driver", "2001-04-02"),
    new (4, "Robert Smith", "cook", "1976-11-21"),
    new (5, "Maria Smith", "accountant", "1986-05-09"),
};

using var fs = new StreamWriter("users.csv");
using var csvWriter = new CsvWriter(fs, csvConfig);

csvWriter.WriteHeader<User>();
csvWriter.NextRecord(); 
csvWriter.WriteRecords(users);

record User(int Id, string Name, string Occupation, string DateOfBirth);

We have a list of users. We decide to quote fields that contain spaces.

var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    ShouldQuote = args => args.Field?.Contains(' ') == true
};

In the CsvConfiguration, we set the ShouldQuote property to return true when a field contains a space. CsvHelper normally quotes fields when required by the CSV format; this rule demonstrates custom quoting.

$ dotnet run
$ cat users.csv 
Id,Name,Occupation,DateOfBirth
1,"John Doe",gardener,1997-12-05
2,"Lucy Smith",teacher,1983-05-12
3,"Roger Roe",driver,2001-04-02
4,"Robert Smith",cook,1976-11-21
5,"Maria Smith",accountant,1986-05-09

C# CSV WriteField

A field in a record is written with WriteField.

Program.cs
using System.Globalization;
using System.Text;
using CsvHelper;

var users = new List<User>
{
    new ("John", "Doe", "gardener"),
    new ("Roger", "Roe", "driver"),
    new ("Lucy", "Smith", "teacher"),
};

using var mem = new MemoryStream();
using var writer = new StreamWriter(mem);
using var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture);

csvWriter.WriteField("FirstName");
csvWriter.WriteField("LastName");
csvWriter.WriteField("Occupation");
csvWriter.NextRecord();

foreach (var user in users)
{
    csvWriter.WriteField(user.FirstName);
    csvWriter.WriteField(user.LastName);
    csvWriter.WriteField(user.Occupation);
    csvWriter.NextRecord();
}

writer.Flush();
var res = Encoding.UTF8.GetString(mem.ToArray());
Console.WriteLine(res);

record User(string FirstName, string LastName, string Occupation);

In the example, we write CSV data to an in-memory stream and then print it.

csvWriter.WriteField("FirstName");
csvWriter.WriteField("LastName");
csvWriter.WriteField("Occupation");
csvWriter.NextRecord();

First, we write the header. The NextRecord method adds a newline.

foreach (var user in users)
{
    csvWriter.WriteField(user.FirstName);
    csvWriter.WriteField(user.LastName);
    csvWriter.WriteField(user.Occupation);
    csvWriter.NextRecord();
}

The WriteField method writes a field to the CSV file. A new record is started with NextRecord.

writer.Flush();

To actually write the data, we need to call Flush.

var result = Encoding.UTF8.GetString(mem.ToArray());
Console.WriteLine(result);

We write the data from the memory to the console.

$ dotnet run
FirstName,LastName,Occupation
John,Doe,gardener
Roger,Roe,driver
Lucy,Smith,teacher

C# CSV write data with WriteRecords

In the following example, we write all the records in one shot with WriteRecords.

Program.cs
using System.Globalization;
using CsvHelper;

var users = new List<User> 
{
    new ("John", "Doe", "gardener"),
    new ("Lucy", "Smith", "teacher"),
    new ("Roger", "Roe", "writer"),
};

using var writer = new StreamWriter(Console.OpenStandardOutput());
using var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture);

csvWriter.WriteHeader<User>();
csvWriter.NextRecord(); // adds new line after header
csvWriter.WriteRecords(users);

record User(string FirstName, string LastName, string Occupation);

In the example, we write the list of user objects to the console. The WriteHeader method writes a header record using the members of the given type.

C# CSV custom solution

For production code, use a CSV library such as CsvHelper. Despite its apparent simplicity, CSV is not easy to parse correctly because fields may be quoted and may contain commas, line breaks, or quotation marks. The following simple parser is suitable only for this deliberately unquoted input.

data.csv
John Doe,gardener,1997-12-05
Jane Doe,teacher,1983-05-10
Robert Smith,driver,2001-04-02
Maria Smith,cook,1976-11-09

This is the data.csv file.

Program.cs
using System.Globalization;
using System.Text;

var path = "data.csv";
var lines = File.ReadLines(path, Encoding.UTF8);

var users = from line in lines
            let fields = line.Split(',')
            select new User(fields[0], fields[1], DateTime.ParseExact(fields[2], "yyyy-MM-dd", CultureInfo.InvariantCulture));

var sorted = from user in users
             orderby user.DateOfBirth descending
             select user;

foreach (var user in sorted)
{
    Console.WriteLine(user);
}

public record User(string Name, string Occupation, DateTime DateOfBirth);

The example parses this simple CSV file with LINQ. It also sorts the users by date of birth in descending order. The ISO dates make parsing independent of the current culture.

$ dotnet run
User { Name = Robert Smith, Occupation = driver, DateOfBirth = 04/02/2001 00:00:00 }
User { Name = John Doe, Occupation = gardener, DateOfBirth = 12/05/1997 00:00:00 }
User { Name = Jane Doe, Occupation = teacher, DateOfBirth = 05/10/1983 00:00:00 }
User { Name = Maria Smith, Occupation = cook, DateOfBirth = 11/09/1976 00:00:00 }

C# export HTML table into CSV file

In the next example, we scrape an HTML table from a web page and export the data to a CSV file.

For web scraping, we use the AngleSharp library.

Program.cs
using System.Globalization;
using AngleSharp;
using CsvHelper;

var config = Configuration.Default.WithDefaultLoader();
using var context = BrowsingContext.New(config);

var url = "https://www.scrapingcourse.com/table-parsing";
using var doc = await context.OpenAsync(url);

var htable = doc.GetElementById("product-catalog")!;
var trs = htable.QuerySelectorAll("tr").Skip(1);

using var fs = new StreamWriter("data.csv");
using var writer = new CsvWriter(fs, CultureInfo.InvariantCulture);

var rows = new List<Row>();

foreach (var tr in trs)
{
    var tds = tr.QuerySelectorAll("td").Take(3);
    var fields = (from e in tds select e.TextContent).ToArray();
    var row = new Row(fields[0], fields[1], fields[2]);

    rows.Add(row);
}

writer.WriteRecords(rows);

record Row(string Rank, string Company, string Sales);

We scrape data from the first table on the page.

var config = Configuration.Default.WithDefaultLoader();
using var context = BrowsingContext.New(config);

var url = "https://www.scrapingcourse.com/table-parsing";
using var doc = await context.OpenAsync(url);

We set up the AngleSharp context and retrieve a document from the provided link.

var htable = doc.GetElementById("product-catalog")!;
var trs = htable.QuerySelectorAll("tr").Skip(1);

We locate the first HTML table and select all its rows except for the header.

using var fs = new StreamWriter("data.csv");
using var writer = new CsvWriter(fs, CultureInfo.InvariantCulture);

We set up the CsvWriter.

foreach (var tr in trs)
{
    var tds = tr.QuerySelectorAll("td").Take(3);
    var fields = (from e in tds select e.TextContent).ToArray();
    var row = new Row(fields[0], fields[1], fields[2]);

    rows.Add(row);
}

We get the data from the first three columns of the table.

writer.WriteRecords(rows);

Finally, the records are written to the file with WriteRecords.

Source

CsvHelper GitHub page

In this article, we have read and written CSV data in C# with the CsvHelper library.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.