
Last updated: October 28, 2026 · Tested on .NET 8 and .NET 10 with CsvHelper 34.0.0 and TinyCsvParser 3.0.11
Quick answer: to read a CSV file in C#, use a CSV parser rather than string.Split(','). The three easiest options in .NET Core and .NET 8+ are CsvHelper (most popular, maps by header name), TinyCsvParser (fast, reports bad rows without crashing) and TextFieldParser (built into .NET, no NuGet package needed).
This guide reads the same CSV file into a typed C# class with each parser, with complete code you can copy. Every example was compiled and run before publishing. At the end you’ll find a comparison table, a CSV-to-JSON example and answers to common questions.
Prerequisites
You need the .NET 8 SDK or later. The TextFieldParser example also runs on older .NET Core 3.1 projects. Create a console app to follow along:
dotnet new console -n CsvReaderDemo
cd CsvReaderDemo
Sample CSV file and model
All three examples read the same users.csv, which has a header row and one value that contains a comma inside quotes:
Id,Name,City,Country
1,Anna Martin,Toronto,Canada
2,Lucas Bernard,Paris,France
3,Priya Shah,"Mumbai, Maharashtra",India

Each row maps to this model class (CsvHelper needs two small additions, shown in its section):
public class UserDetails
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string City { get; set; } = "";
public string Country { get; set; } = "";
}
In the project file, set the CSV to copy to the output folder so the app can find it at run time:
<ItemGroup>
<None Update="users.csv" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
Approach 1: Read CSV with TinyCsvParser
TinyCsvParser is a small, high-performance CSV library. Each row comes back as either a parsed object or an error with its line number, so one bad row never stops the whole import. Install it:
dotnet add package TinyCsvParser
Create a mapping class that links CSV header names to properties (you can also map by column index with MapProperty(0, x => x.Id)):
using TinyCsvParser;
public class UserDetailsMapping : CsvMapping<UserDetails>
{
public UserDetailsMapping()
{
MapProperty("Id", x => x.Id);
MapProperty("Name", x => x.Name);
MapProperty("City", x => x.City);
MapProperty("Country", x => x.Country);
}
}
Then set the CSV options, create the parser and read the file:
using System.Text;
using TinyCsvParser;
using TinyCsvParser.Models;
var options = new CsvOptions(
Delimiter: ',',
QuoteChar: '"',
EscapeChar: '"',
Encoding: Encoding.UTF8,
SkipHeader: true);
var parser = new CsvParser<UserDetails>(options, new UserDetailsMapping());
foreach (var row in parser.ReadFromFile("users.csv"))
{
if (row.IsSuccess)
{
var user = row.Result;
Console.WriteLine($"{user.Id}: {user.Name}, {user.City}, {user.Country}");
}
else if (row.IsError)
{
Console.WriteLine($"Line {row.LineNumber}: {row.Error}");
}
}
Output:

If a row has a value that can’t be converted, such as abc in the Id column, you get a message like Line 5: Error at Column 0: Conversion failed: abc and the parser moves on to the next row. Parsing is lazy: rows are read one at a time as you loop, so large files don’t fill memory. Use ReadFromFileAsync with await foreach in async code.
Upgrading from TinyCsvParser 2.x? Version 3 replaced
CsvParserOptionswithCsvOptions, renamedIsValidtoIsSuccess, and reports errors with aLineNumber. Older tutorials that usenew CsvParserOptions(true, ',')won’t compile against the current package.
Approach 2: Read CSV with CsvHelper
CsvHelper is the most widely used CSV library for .NET. It matches columns to properties by header name, so the column order in the file doesn’t matter, and it can write CSV too. Install it:
dotnet add package CsvHelper
CsvHelper builds the mapping code at compile time. Mark the model with [CsvRecord] and make it partial:
using CsvHelper;
[CsvRecord]
public partial class UserDetails
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string City { get; set; } = "";
public string Country { get; set; } = "";
}
Then read the file:
using CsvHelper;
using var reader = CsvReader.FromFile("users.csv");
foreach (UserDetails user in reader.GetRecords<UserDetails>())
{
Console.WriteLine($"{user.Id}: {user.Name}, {user.City}, {user.Country}");
}
GetRecords streams rows one at a time, so memory stays flat even for very large files. Add .ToList() only when you need every row in memory at once.
When CSV headers don’t match your property names
Use [CsvColumn] to point a property at a different header. For a file with the headers UserId,Full Name,City,Country:
[CsvRecord]
public partial class UserRow
{
[CsvColumn("UserId")]
public int Id { get; set; }
[CsvColumn("Full Name")]
public string Name { get; set; } = "";
public string City { get; set; } = "";
public string Country { get; set; } = "";
}
Reading individual fields
If you don’t want a class at all, read field by field:
using var reader = CsvReader.FromFile("users.csv");
while (reader.Read())
{
int id = reader.Parse<int>("Id");
string name = reader.GetString("Name");
string city = reader.GetString("City");
Console.WriteLine($"{id}: {name} ({city})");
}
Using code from an older CsvHelper tutorial? CsvHelper 34 is a rewrite.
new CsvReader(reader, CultureInfo.InvariantCulture),CsvConfigurationandClassMap<T>are the version 33 API and no longer compile. See the official CsvHelper 34 migration guide for before-and-after examples.
For more options, such as writing CSV files, see our complete CsvHelper guide.
Approach 3: Read CSV with TextFieldParser (no NuGet package)
TextFieldParser ships with .NET Core 3.0 and later in the Microsoft.VisualBasic.FileIO namespace, so you don’t need any NuGet package. You can use it from C# like any other class. It handles quoted values correctly, but it returns each row as a string[], so you map fields to your class yourself:
using Microsoft.VisualBasic.FileIO;
var users = new List<UserDetails>();
using var parser = new TextFieldParser("users.csv");
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters(",");
parser.HasFieldsEnclosedInQuotes = true;
parser.ReadLine(); // skip the header row
while (!parser.EndOfData)
{
string[] fields = parser.ReadFields() ?? Array.Empty<string>();
users.Add(new UserDetails
{
Id = int.Parse(fields[0]),
Name = fields[1],
City = fields[2],
Country = fields[3]
});
}
foreach (var user in users)
{
Console.WriteLine($"{user.Id}: {user.Name}, {user.City}, {user.Country}");
}
With HasFieldsEnclosedInQuotes set, the value "Mumbai, Maharashtra" comes back as one field instead of being split in two. If a line is badly formed, ReadFields() throws a MalformedLineException, which includes the line number so you can log it and move on.
For a deeper walkthrough, see our TextFieldParser guide.
Common pitfalls with any approach
- Splitting on commas yourself.
line.Split(',')breaks on quoted values like"Mumbai, Maharashtra". Use a parser. - Encoding. Files saved from Excel are often UTF-8 with a byte-order mark, or Windows-1252. Pass the encoding explicitly if names with accents come out garbled.
- Header row. Skip it explicitly (TinyCsvParser, TextFieldParser) or let CsvHelper use it for mapping. Don’t parse it as data.
- Large files. Stream rows instead of loading everything with
ToList()orFile.ReadAllLines.
CsvHelper vs TinyCsvParser vs TextFieldParser: which should you use?
For most projects, start with CsvHelper: it maps by header name, streams large files and also writes CSV. Choose TinyCsvParser when you need a clear error for every bad row, and TextFieldParser when you can’t add a NuGet package.
| Approach | NuGet package | Maps columns by | Returns | Best for |
|---|---|---|---|---|
| CsvHelper | CsvHelper | Header name (or [CsvColumn]) | Typed objects | Most apps, and anything that also writes CSV |
| TinyCsvParser | TinyCsvParser | Header name or index | Typed objects + per-row errors with line numbers | Imports where bad rows must be reported, not crash the run |
| TextFieldParser | None (built in) | Manual | string[] per row | Small tools with no extra dependencies |
Bonus: convert the CSV to JSON
Free and easy tool :
Doest store data to cloud. Your information remains with you.
Using Code
Once the rows are in a List<UserDetails>, the built-in System.Text.Json turns them into JSON in one call:
using System.Text.Json;
string json = JsonSerializer.Serialize(users, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText("users.json", json);
Frequently asked questions
How do I read a CSV file in C# without a library?
Use TextFieldParser from the Microsoft.VisualBasic.FileIO namespace. It’s built into .NET Core 3.0 and later and handles quoted values. Avoid File.ReadAllLines with Split(','), which breaks on commas inside quotes.
How do I skip the header row?
With TinyCsvParser, set SkipHeader: true in CsvOptions. With TextFieldParser, call parser.ReadLine() once before the loop. CsvHelper reads the header automatically and uses it to match columns.
How do I read a large CSV file efficiently?
Loop over the rows instead of loading them all at once. CsvHelper’s GetRecords and TinyCsvParser’s ReadFromFile both read one row at a time, so memory use stays low however big the file is.
How do I read a CSV file with a semicolon or tab separator?
In TinyCsvParser, set Delimiter: ';' (or '\t') in CsvOptions. In TextFieldParser, call parser.SetDelimiters(";"). In CsvHelper, pass new CsvReaderOptions { Separator = ';' } to CsvReader.FromFile.
Summary
All three approaches read the same CSV file into the same typed list. Don’t split lines on commas yourself, pick a parser that handles quoted values, and stream rows when files get large. Every example in this article was compiled and run before publishing (.NET 8 and .NET 10).
References
- CsvHelper on NuGet (version 34.0.0)
- CsvHelper 34 migration guide
- TinyCsvParser on NuGet (version 3.0.11)
- TinyCsvParser documentation

Thanks for all options.Did not know about Textfieldparser before .. thank you ?
Thank you for the feedback
Thanks. This article made my job easy ?.
Thanks Leon. I am glad article helped you.Thanks for the feedback!