Reading from a CSV file in C# is a common operation. In this article, we are going to show how to easily read data from a CSV file in C# using the CSVHelper NuGet package.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Read Data From a CSV File With Headers

In a previous article, we had already shown how to write to a CSV file.  That said, we strongly recommend reading that article first because the reading operation is very similar to writing, and we won’t explain the same concepts here.

So again, we are going to create a new console project and will try reading the same CSV file, that we created in the previous article.

Reading from it is very similar to writing to it:

using (var reader = new StreamReader("filePersons.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    var records = csv.GetRecords<Person>();
}

As we can compare with writing to a CSV file, we use a StreamReader instead of a StreamWriter, and use the CsvReader instead of the CsvWriter. We then map the data in the CSV file to our own Person class:

public class Person
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsLiving { get; set; }
    public DateTime DateOfBirth { get; set; }
}

Reading dates is also very straightforward – it will automatically deduce its format from the CSV file.

Read Data From a CSV File Without Headers

What happens when there are no headers present? How can we tell which column needs to be mapped to which property of our Person class? 

Well, we can do that by specifying that headers are not present:

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

using (var reader = new StreamReader("filePersons.csv"))
using (var csv = new CsvReader(reader, configuration))
{
    var records = csv.GetRecords<Person>();
}

There are two methods for mapping columns to properties in our class.

The first is to use annotations to specify the index:

public class Person
{
    [Index(0)]
    public int Id { get; set; }
    [Index(1)]
    public string? Name { get; set; }
    [Index(2)]
    public bool IsLiving { get; set; }
}

Even in case the fields are all in order, like in this example, we can’t rely on that fact, so still, the index attributes need to be present.

The second, and more powerful way, is to use a mapping:

public class PersonMap : ClassMap<Person>
{
    public PersonMap()
    {
        Map(p => p.Id).Index(0); 
        Map(p => p.Name).Index(1);
        Map(p => p.IsLiving).Index(2);
    }
}

We can create a mapping by deriving from ClassMap<T>, which is in the CsvHelper.Configuration namespace. By using a mapping, we don’t need to annotate the fields in our Person class. This can be handy, when said class is outside of our control and when we can’t make changes to it. In our mapping code, we map the first index (index 0) to the Id field, the second index to the Name field etc.

To use this map, we need to register it in the context:

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<
using (var reader = new StreamReader("filePersons.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    csv.Context.RegisterClassMap<PersonMap>();
    var records = csv.GetRecords<Person>();
}

Comments and Delimiters

The CSV file can contain comments and can use a different delimiter than the comma. Of course, this can be specified in the CsvConfiguration  as well:

var configuration = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Delimiter = ";",
    Comment = '%'
};

The default delimiter is , while the default comment is #. When using a different value, we can thus specify it in the configuration, which we pass into the CsvReader.

Conclusion

In this article, we’ve seen how easy it is to read from a CSV file using CSVHelper. We also saw that it is easy to read when there are no headers present, or to use a different delimiter or comment.