How to Export List to CSV in C# (Native & CsvHelper Examples)
Data portability is a requirement for almost every enterprise application. Whether you are building an administrative dashboard, a reporting tool, or a data migration script, you will eventually need to export a List of Objects to CSV in C#.
While CSV (Comma Separated Values) looks like a simple text format, generating it correctly requires handling specific edge cases—such as data containing commas, quotes, or newlines.
This guide covers everything from a quick CsvHelper implementation to a dependency-free Native StringBuilder approach, ensuring your application handles data strictly according to RFC 4180 standards.
Quick Answer: The Fastest Way
If you need to convert a list to CSV immediately and can use a library, CsvHelper is the industry standard. It handles memory management and edge cases (like commas in names) automatically.
Code Snippet:
using CsvHelper; using System.Globalization; using System.IO; public void ExportData(List<Employee> data, string filePath) { using (var writer = new StreamWriter(filePath)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { csv.WriteRecords(data); } }
Why CSV Export is Critical
CSV remains the universal language of data exchange. It is lightweight, human-readable, and compatible with virtually every spreadsheet software (Microsoft Excel, Google Sheets) and database system.
However, writing a CSV writer from scratch is often deceptive. A simple string.Join(",", values) works for simple data but fails immediately when a user enters a name like "Smith, John". Without proper handling, that comma splits the name into two columns, corrupting your dataset.
This guide contrasts two methods:
- CsvHelper: The robust, automatic solution.
- Native C#: The lightweight, manual solution (great for zero-dependency environments).
Prerequisites & Setup
To follow this guide, ensure you have:
- .NET 6, .NET 8, or newer SDK installed.
- Visual Studio 2022 or VS Code.
For the first method, we will use the popular NuGet package CsvHelper.
Install via NuGet Package Manager Console:
Install-Package CsvHelper
Employee class and data list:public class Employee{public int Id { get; set; }public string Name { get; set; }public string Title { get; set; }public decimal Salary { get; set; }}// Sample Datavar employees = new List<Employee>{new Employee { Id = 1, Name = "Nikunj Satasiya", Title = "Developer", Salary = 85000 },new Employee { Id = 2, Name = "Smith, John", Title = "Manager", Salary = 95000 }, // Note the comma!new Employee { Id = 3, Name = "Bob \"The Builder\"", Title = "Contractor", Salary = 60000 } // Note the quotes!};
Method 1: The Robust Approach (CsvHelper)
The CsvHelper library is the preferred method for most .NET developers. It relies on reflection to map your class properties to CSV headers automatically.
Why use this method?
- Automatic Escaping: It automatically handles fields containing commas, newlines, or double quotes.
- Type Conversion: It handles Dates, Decimals, and Booleans using the correct
CultureInfo. - Speed: It is highly optimized for memory and speed using
Span<T>.
The Implementation
Here is how to write the list to a file:
using CsvHelper;using System.Globalization;using System.IO;using System.Text;public static void WriteToCsvWithLibrary(List<Employee> employees, string filePath){// Use UTF8 encoding to ensure special characters are handled correctlyusing (var writer = new StreamWriter(filePath, false, Encoding.UTF8))using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)){csv.WriteRecords(employees);}}
Id,Name,Title,Salary1,Nikunj Satasiya,Developer,850002,"Smith, John",Manager,950003,"Bob ""The Builder""",Contractor,60000
Note: It also escaped the internal quotes in Bob's name by doubling them (""), complying with CSV standards.
Method 2: The Native Approach (StringBuilder)
Sometimes you cannot add external libraries due to company policy, or you are writing a lightweight Azure Function and want to keep the deployment size small. In these cases, use the native StringBuilder.
The Challenge: "Edge Cases"
If you simply do this:
sb.AppendLine($"{emp.Id},{emp.Name},{emp.Title}");
You will generate broken CSV files whenever a comma appears in the data. You must manually sanitize the data.
The Solution: Manual Escaping
We need a helper method that checks if a string contains a comma or a quote. If it does, we must wrap the string in quotes and escape any existing quotes.
using System.Collections.Generic;using System.IO;using System.Text;public static class NativeCsvExporter{public static void WriteToCsvNative(List<Employee> employees, string filePath){var sb = new StringBuilder();// 1. Write the Headersb.AppendLine("Id,Name,Title,Salary");// 2. Loop through dataforeach (var emp in employees){var line = string.Format("{0},{1},{2},{3}",emp.Id,EscapeCsvValue(emp.Name),EscapeCsvValue(emp.Title),emp.Salary);sb.AppendLine(line);}// 3. Save to fileFile.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);}// CRITICAL: Handles commas and quotesprivate static string EscapeCsvValue(string value){if (string.IsNullOrEmpty(value)) return "";// Check if value contains comma, newline, or double quoteif (value.Contains(",") || value.Contains("\"") || value.Contains("\n")){// Replace double quotes with double-double quotes (CSV standard)value = value.Replace("\"", "\"\"");// Wrap the entire value in quotesreturn $"\"{value}\"";}return value;}}
Key Takeaway: The EscapeCsvValue function mimics what CsvHelper does automatically. Without this function, your CSV export is prone to corruption.
Advanced: Asynchronous Export (Async/Await)
In modern .NET applications, especially web APIs, you should avoid blocking the main thread with File I/O operations. Using Async methods ensures your server remains responsive while the file is being written.
Here is the Async version using CsvHelper:
public static async Task WriteToCsvAsync(List<Employee> employees, string filePath){using (var writer = new StreamWriter(filePath, false, Encoding.UTF8))using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)){// Use WriteRecordsAsyncawait csv.WriteRecordsAsync(employees);}}
To call this method:
await WriteToCsvAsync(employees, "data.csv");
Performance: Streaming Large Datasets
List<T> before writing to CSV will likely cause an OutOfMemoryException.The solution is to use streaming with IEnumerable and yield return. This processes one record at a time, keeping memory usage low.
1. Create a Data Generator (Simulator):
public static IEnumerable<Employee> GetLargeDataset(){for (int i = 0; i < 1000000; i++){// yield return generates data one by one, not all at onceyield return new Employee{Id = i,Name = $"Worker {i}",Title = "Staff",Salary = 50000};}}
2. Stream to CSV:
public static async Task ExportLargeData(string filePath){var dataStream = GetLargeDataset(); // Does not execute yetusing (var writer = new StreamWriter(filePath))using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)){// Writes rows one by one as they are generatedawait csv.WriteRecordsAsync(dataStream);}}
This approach ensures your application's memory footprint remains flat, regardless of whether you are exporting 1,000 or 1,000,000 rows.
ASP.NET Core: Downloading the CSV
A common requirement is creating a "Download Report" button in a web application. You generally return a FileResult (specifically a FileStreamResult or FileContentResult) from your Controller.
Here is how to generate and download a CSV in an ASP.NET Core Controller without saving it to the server's disk first (using MemoryStream):
[HttpGet("download-report")]public IActionResult DownloadCsv(){var employees = _repository.GetEmployees(); // Fetch your datausing (var memoryStream = new MemoryStream())using (var streamWriter = new StreamWriter(memoryStream))using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture)){csvWriter.WriteRecords(employees);streamWriter.Flush(); // Ensure all data is written to the memory streamreturn File(memoryStream.ToArray(), "text/csv", "Employees.csv");}}
Conclusion
Converting a List of Objects to CSV in C# varies in complexity depending on your needs:
- For 95% of use cases: Use CsvHelper. It is safer, faster to write, and handles the "comma in name" edge case automatically.
- For zero-dependency cases: Use
StringBuilder, but ensure you implement theEscapeCsvValuelogic to wrap text in quotes where necessary. - For Big Data: Always combine CsvHelper with
IEnumerableandyield returnto prevent memory overflows.
By following the patterns in this guide, you ensure your data exports are robust, compliant with CSV standards, and performant enough for enterprise-grade applications.
