Convert List to CSV in C# With Example

Converting a list to a CSV file in C# is a common requirement in software development, especially when exporting data for reports or data sharing. This article provides a step-by-step tutorial on how to convert a list to a CSV file in C#, first with plain .NET code and then with the CsvHelper library. The examples target .NET 6 or later.

Convert List to CSV in C# With Example

Why Convert a List to a CSV File?

CSV (Comma-Separated Values) is a widely used file format for storing tabular data. It's lightweight, human-readable, and easy to process programmatically. Exporting lists to CSV in C# is particularly useful for applications that deal with:

  • Data reporting.
  • Data interchange between systems.
  • Archiving structured information.

Method 1: C# List to CSV File Without a Library

To convert a list to a CSV file in C# without extra packages, we will:

  • Create a sample list of objects.
  • Use StringBuilder to generate CSV content.
  • Write the CSV content to a file.

Here is the step-by-step implementation.

Step 1: Define a Sample Data Model

The model below has three public properties. Each property becomes one CSV column.

The two string properties are initialized with string.Empty so the compiler doesn't warn about non-nullable properties when nullable reference types are enabled, which is the default in new .NET 6+ projects.

csharp
public class Person
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
    public string Email { get; set; } = string.Empty;
}

Step 2: Create a List of Objects

csharp
List<Person> people = new List<Person>
{
    new Person { Name = "Nikunj Satasiya", Age = 30, Email = "nikunj.satasiya@example.com" },
    new Person { Name = "Mansi Satasiya", Age = 29, Email = "mansi.satasiya@example.com" },
    new Person { Name = "Hiren Dobariya", Age = 31, Email = "hiren.dobariya@example.com" }
};

Step 3: Convert the List to CSV Format

The helper below uses reflection to read the public properties of any type, writes the property names as the header row, and then writes one row per item. It lives in a static class so it can be called from anywhere.

The snippets in this article rely on the implicit using directives that are enabled by default in .NET 6+ console projects (for example System.Linq, System.IO and System.Collections.Generic). If your project has implicit usings turned off, add those namespaces yourself.

If you use a top-level Program.cs, C# requires type declarations such as Person and CsvExporter to appear after all top-level statements in that file. The simplest option is to place each class in its own file.

csharp
using System.Globalization;
using System.Reflection;
using System.Text;

public static class CsvExporter
{
    private static readonly char[] SpecialCharacters = { ',', '"', '\r', '\n' };

    public static string ConvertListToCsv<T>(IEnumerable<T> items)
    {
        ArgumentNullException.ThrowIfNull(items);

        var csvBuilder = new StringBuilder();
        var properties = typeof(T)
            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.GetGetMethod() != null && p.GetIndexParameters().Length == 0)
            .ToArray();

        csvBuilder.AppendLine(string.Join(",", properties.Select(p => Escape(p.Name))));

        foreach (var item in items)
        {
            if (item == null)
            {
                continue;
            }

            var row = properties.Select(p =>
                Escape(Convert.ToString(p.GetValue(item), CultureInfo.InvariantCulture)));

            csvBuilder.AppendLine(string.Join(",", row));
        }

        return csvBuilder.ToString();
    }

    private static string Escape(string? value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return string.Empty;
        }

        if (value.IndexOfAny(SpecialCharacters) >= 0)
        {
            return "\"" + value.Replace("\"", "\"\"") + "\"";
        }

        return value;
    }
}

A few details in this code are worth understanding. The first line of output is the header, built from the property names. Each following line is one item, with every property value converted to text. Null values become empty fields.

The Escape method follows the usual CSV quoting rules. A field that contains a comma, a double quote or a line break is wrapped in double quotes, and any double quote inside it is doubled. Without this step, a value such as Smith, John would be split into two columns.

Values are converted with CultureInfo.InvariantCulture. Otherwise a decimal like 1.5 could be written as 1,5 on a machine with a European culture, which would break the column layout.

The helper reads only public instance properties that have a public getter. The BindingFlags.Public | BindingFlags.Instance argument and the Where filter enforce this. Fields, static properties, write-only properties and indexers are skipped, because reading an indexer or a property without a getter would throw an exception.

Complex values such as nested objects are written using their ToString() result, which for most classes is just the type name. For example, an Address property would appear as MyApp.Address. For those cases, map the data to a flat model first.

The properties are read from the compile-time type T, not from each object's runtime type. For example, a List<object> produces no columns, and a sequence typed as an interface includes only that interface's properties.

A null sequence throws an ArgumentNullException, while null items inside the sequence are skipped. Reflection also doesn't guarantee a property order. In practice it is normally the declaration order, but if column order matters, don't rely on it.

Finally, AppendLine uses the platform's newline (CRLF on Windows, LF on Linux and macOS). Nearly all CSV readers accept either. RFC 4180 specifies CRLF, so if you need strict compliance on every platform, append "\r\n" explicitly instead of using AppendLine.

Step 4: Save the CSV Content to a File

csharp
using System.Text;

string csvContent = CsvExporter.ConvertListToCsv(people);
File.WriteAllText("people.csv", csvContent, new UTF8Encoding(true));
Console.WriteLine("CSV file created successfully.");

The file is written as UTF-8 with a byte order mark (BOM). The BOM helps Excel detect the encoding so that non-English characters display correctly. Some other tools may show the BOM as an extra character at the start of the file, so omit it (use new UTF8Encoding(false)) if the file is meant for systems rather than Excel users.

A relative path such as "people.csv" is created in the application's current working directory. When you run from Visual Studio, that is usually the output folder (for example bin/Debug/net8.0). When you use dotnet run, it is usually the project folder. Use a full path if you need the file somewhere specific.

The resulting people.csv looks like this:

text
Name,Age,Email
Nikunj Satasiya,30,nikunj.satasiya@example.com
Mansi Satasiya,29,mansi.satasiya@example.com
Hiren Dobariya,31,hiren.dobariya@example.com

Method 2: Convert a List to CSV Using CsvHelper

If you prefer using a third-party library, here's how to do it with CsvHelper. It takes care of quoting, escaping and formatting for you.

Step 1: Install CsvHelper

Run the following command in the NuGet Package Manager Console:

powershell
Install-Package CsvHelper

Or, if you use the .NET CLI, run dotnet add package CsvHelper in your project folder.

Step 2: Write the CSV File

csharp
using CsvHelper;
using System.Globalization;
using System.IO;
using System.Text;

using (var writer = new StreamWriter("people.csv", false, new UTF8Encoding(true)))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
    csv.WriteRecords(people);
}

Console.WriteLine("CSV file created successfully using CsvHelper.");

WriteRecords writes a header row from the property names, followed by one row per item. Passing CultureInfo.InvariantCulture keeps number and date formatting consistent regardless of the machine's regional settings. For async code, CsvHelper also provides WriteRecordsAsync.

Note that a plain StreamWriter writes UTF-8 without a BOM. The example above passes new UTF8Encoding(true) so the output matches the manual approach and opens correctly in Excel. Remove it if you don't want a BOM.

Optional: Protect Against CSV Injection

Recent versions of CsvHelper can guard against formula injection through the InjectionOptions setting. Older versions used a SanitizeForInjection setting instead. The protection is off by default. To enable it, pass a CsvConfiguration to the writer:

csharp
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.IO;
using System.Text;

var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    InjectionOptions = InjectionOptions.Escape
};

using (var writer = new StreamWriter("people.csv", false, new UTF8Encoding(true)))
using (var csv = new CsvWriter(writer, config))
{
    csv.WriteRecords(people);
}

The available options are None (the default), Escape, Strip and Exception. Escape follows the OWASP recommendations. Test it with your own data before relying on it, because legitimate values such as negative numbers also start with -.

C# Generate CSV from List

This example shows how to generate a CSV string from a list, using the CsvExporter helper from Method 1, for scenarios where you don't need to save a file but want to use the CSV data programmatically:

csharp
string csvContent = CsvExporter.ConvertListToCsv(people);
Console.WriteLine(csvContent);

A CSV string is useful when the data goes somewhere other than the disk. For example, you can send it as an email attachment, store it in a database or cloud storage, or return it from a web endpoint as a downloadable file.

C# Convert IEnumerable to CSV

The ConvertListToCsv method already accepts IEnumerable<T>, and List<T> implements that interface. This means the same method works for arrays, LINQ query results and any other sequence, with no changes:

csharp
IEnumerable<Person> adults = people.Where(p => p.Age >= 30);

string adultsCsv = CsvExporter.ConvertListToCsv(adults);
Console.WriteLine(adultsCsv);

The method enumerates the sequence once, so a lazy LINQ query is evaluated at that point.

Plain C# or CsvHelper: Which Should You Use?

For smaller projects, a straightforward approach without additional libraries may be sufficient. The StringBuilder method we used earlier is lightweight and works well for flat objects with simple values.

If you need custom column names, a specific column order, custom date or number formats, or nested objects, a library such as CsvHelper will save you from rebuilding that logic yourself. It is also usually the safer choice for larger or more complex exports.

Export List to CSV in C#: Best Practices

  • Handle Special Characters: Fields containing commas, quotes or line breaks must be enclosed in quotes, with inner quotes doubled. The Escape method above does this, and CsvHelper does it automatically.
  • Validate Data: Check for null or invalid values before writing. In the helper above, null values are written as empty fields and null items in the list are skipped.
  • Watch Out for Formula Injection: If the data comes from users and the file will be opened in Excel, text values starting with =, +, - or @ can be interpreted as formulas. OWASP also lists a leading tab or carriage return. Sanitize such text values, for example by following the OWASP guidance on CSV injection. Apply this to text columns only, because a legitimate negative number also starts with -. If you use CsvHelper, its InjectionOptions setting can do this for you, as shown in the CsvHelper section above.
  • Mind Encoding and Culture: Use UTF-8 (with a BOM if Excel users are your audience) and invariant culture for numbers and dates, unless you deliberately want locale-specific formatting. For dates, prefer an unambiguous format such as ISO 8601 (yyyy-MM-dd). Note that the manual helper writes a DateTime in the invariant culture's month-first format (for example 01/31/2025 00:00:00), so format dates yourself if you want ISO 8601. CsvHelper's mapping options let you set a format per property.
  • Consider Excel's Regional Delimiter: Excel uses the list separator from the operating system's regional settings. In locales where that separator is a semicolon, a comma-delimited file may open as a single column. In that case, import it through Excel's Data > From Text/CSV option or choose a delimiter that matches your audience. Excel also recognizes an optional first line of sep=, that tells it which delimiter to use. This is an Excel-specific convention: other CSV parsers will treat that line as data, and behavior can vary between Excel versions, so use it only for files meant to be opened in Excel.
  • Stream Large Exports: The StringBuilder approach keeps the entire file in memory. For very large datasets, write rows directly to a StreamWriter instead.
  • Measure Reflection Overhead: Reflection is convenient, but reading values with GetValue is slower than direct property access. It is fine for small and medium lists. For high-volume exports, measure first, and if it becomes a bottleneck, write the columns explicitly or use CsvHelper.
  • Consider Trimming and Native AOT: The reflection-based helper is not trim-safe or Native AOT-safe. When trimming is enabled, the trimmer cannot know which properties will be read at runtime, so it may remove them, which can lead to missing columns and trim warnings at build time. If you publish with trimming or Native AOT, write the columns explicitly instead. CsvHelper also relies on reflection, so check its documentation before using it in those scenarios.
  • Use a Library: For complex CSV files, consider using libraries like CsvHelper.

Conclusion

Converting a list to a CSV file in C# involves three steps:

  1. Creating a data model and populating the list.
  2. Formatting the data as CSV using StringBuilder or CsvHelper, with proper escaping.
  3. Writing the CSV content to a file with a suitable encoding.

Whether you're creating reports, exporting data, or sharing structured information, this is a valuable skill. This article covered two approaches: a manual method using StringBuilder with proper escaping, and the CsvHelper library for more complex needs.

Pick the approach that suits your project, and always check how special characters, encoding and culture are handled.

Codingvila provides articles and blogs on web and software development for beginners as well as free Academic projects for final year students in Asp.Net, MVC, C#, Vb.Net, SQL Server, Angular Js, Android, PHP, Java, Python, Desktop Software Application and etc.

If you have any questions, contact us on info.codingvila@gmail.com