CsvHelper для C# (.NET): полное руководство с примерами
1. Что это и зачем нужна
Библиотека CsvHelper — это мощный и гибкий инструмент для чтения и записи CSV-файлов в экосистеме .NET. Она предоставляет разработчикам высокоуровневый API, который абстрагирует сложность работы с CSV-форматом, позволяя сосредоточиться на бизнес-логике приложения. В отличие от примитивных методов, таких как String.Split(','), CsvHelper корректно обрабатывает экранированные символы, кавычки, многострочные поля и различные культурные настройки (разделители, форматы дат и чисел).
Зачем она нужна? В современной разработке CSV остаётся одним из самых распространённых форматов для обмена данными: экспорт отчётов, импорт из Excel, загрузка конфигураций, обмен с устаревшими системами и миграция данных. CsvHelper превращает рутинную задачу парсинга в чистый, декларативный код, используя маппинг классов (атрибуты или Fluent API), автоматическое преобразование типов и встроенную валидацию. Это экономит часы разработки и снижает количество ошибок, связанных с ручным разбором строк.
Ключевые возможности: поддержка .NET Standard 2.0+ (работает везде: .NET Framework, .NET Core, .NET 5+), потоковая обработка (не загружает весь файл в память), кастомные конвертеры типов, детектирование разделителей, работа с заголовками и индексами, а также полная поддержка асинхронных операций (ReadAsync/WriteAsync).
2. Установка
Установка производится через менеджер пакетов NuGet. Выполните одну из следующих команд в консоли диспетчера пакетов или в терминале .NET CLI:
dotnet add package CsvHelper
или через Package Manager Console:
Install-Package CsvHelper
Для версии 31.0.2 (актуальная на момент написания) требуется .NET Standard 2.0 или выше. После установки добавьте using-директиву в ваш код:
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
3. Быстрый старт — минимальный рабочий пример
Создадим простую модель и запишем/прочитаем CSV-файл.
using System.Globalization;
using CsvHelper;
using CsvHelper.Configuration;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
// Запись
var people = new List<Person>
{
new Person { Name = "Анна", Age = 30, Email = "anna@example.com" },
new Person { Name = "Борис", Age = 25, Email = "boris@example.com" }
};
using (var writer = new StreamWriter("people.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(people);
}
// Чтение
using (var reader = new StreamReader("people.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var records = csv.GetRecords<Person>().ToList();
foreach (var p in records)
Console.WriteLine($"{p.Name}, {p.Age}, {p.Email}");
}
Этот пример демонстрирует базовый цикл: создание объекта CsvWriter или CsvReader, передача потока и культуры, вызов метода записи/чтения. Обратите внимание на CultureInfo.InvariantCulture — это гарантирует, что десятичные разделители и даты будут записаны в едином формате (точка для чисел, ISO для дат).
4. Основные методы/функции/классы
Рассмотрим ключевые классы и методы библиотеки. Каждый метод сопровождается сигнатурой, описанием и рабочим примером.
4.1. CsvReader.GetRecords<T>()
Сигнатура: public IEnumerable<T> GetRecords<T>()
Что делает: Читает все записи из CSV-файла и маппит их на указанный тип T. Возвращает ленивую последовательность (IEnumerable), что позволяет обрабатывать большие файлы без полной загрузки в память.
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
using (var reader = new StreamReader("products.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var products = csv.GetRecords<Product>();
foreach (var product in products)
Console.WriteLine($"{product.Name}: {product.Price:C}");
}
4.2. CsvWriter.WriteRecords<T>()
Сигнатура: public void WriteRecords(IEnumerable<T> records)
Что делает: Записывает коллекцию объектов в CSV-файл. Автоматически создаёт строку заголовков на основе свойств класса.
var orders = new List<Order>
{
new Order { Id = 1, Date = DateTime.Now, Total = 150.50m },
new Order { Id = 2, Date = DateTime.Now.AddDays(-1), Total = 89.99m }
};
using (var writer = new StreamWriter("orders.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(orders);
}
4.3. CsvContext.RegisterClassMap<TMap>()
Сигнатура: public void RegisterClassMap<TMap>() where TMap : ClassMap
Что делает: Регистрирует пользовательский маппинг (ClassMap) для тонкой настройки соответствия между свойствами класса и колонками CSV. Позволяет задавать имена колонок, порядок, конвертеры и игнорировать поля.
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Map(m => m.Name).Name("full_name");
Map(m => m.Age).Index(1);
Map(m => m.Email).Ignore();
}
}
var config = new CsvConfiguration(CultureInfo.InvariantCulture);
using (var reader = new StreamReader("people.csv"))
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<PersonMap>();
var records = csv.GetRecords<Person>().ToList();
}
4.4. CsvConfiguration.HasHeaderRecord
Сигнатура: public bool HasHeaderRecord { get; set; } (свойство конфигурации)
Что делает: Определяет, содержит ли CSV-файл строку заголовков. По умолчанию true. Если файл не имеет заголовков, установите false и используйте маппинг по индексу.
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
HasHeaderRecord = false
};
using (var reader = new StreamReader("data_no_header.csv"))
using (var csv = new CsvReader(reader, config))
{
// Предполагаем, что колонки идут в порядке: Name, Age
csv.Context.RegisterClassMap<PersonMapByIndex>();
var records = csv.GetRecords<Person>().ToList();
}
public class PersonMapByIndex : ClassMap<Person>
{
public PersonMapByIndex()
{
Map(m => m.Name).Index(0);
Map(m => m.Age).Index(1);
}
}
4.5. CsvReader.ReadAsync()
Сигнатура: public async Task<bool> ReadAsync()
Что делает: Асинхронно читает следующую строку CSV. Используется для ручного управления процессом чтения, когда требуется кастомная обработка каждой строки.
using (var reader = new StreamReader("large_file.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
await csv.ReadAsync();
csv.ReadHeader(); // читаем заголовки
while (await csv.ReadAsync())
{
var name = csv.GetField<string>("Name");
var age = csv.GetField<int>("Age");
Console.WriteLine($"Name: {name}, Age: {age}");
}
}
4.6. CsvWriter.WriteField<T>()
Сигнатура: public void WriteField<T>(T value)
Что делает: Записывает одно значение в текущую строку CSV. Полезно для ручного построения строк или записи данных, не привязанных к модели.
using (var writer = new StreamWriter("manual.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
// Записываем заголовки вручную
csv.WriteField("ID");
csv.WriteField("Value");
csv.NextRecord();
// Записываем данные
for (int i = 0; i < 5; i++)
{
csv.WriteField(i);
csv.WriteField($"Item_{i}");
csv.NextRecord();
}
}
4.7. CsvHelper.TypeConversion.DateTimeConverter
Сигнатура: public class DateTimeConverter : DefaultTypeConverter
Что делает: Стандартный конвертер для типа DateTime. Можно переопределить для работы с нестандартными форматами дат. Используется в связке с TypeConverterOptions.
var config = new CsvConfiguration(CultureInfo.InvariantCulture);
using (var reader = new StreamReader("dates.csv"))
using (var csv = new CsvReader(reader, config))
{
// Настраиваем формат даты для всех DateTime полей
csv.Context.TypeConverterOptionsCache.GetOptions<DateTime>().Formats = new[] { "dd.MM.yyyy" };
var records = csv.GetRecords<Event>().ToList();
}
public class Event
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
4.8. CsvHelper.Configuration.Attributes.IndexAttribute
Сигнатура: [AttributeUsage(AttributeTargets.Property)] public class IndexAttribute : Attribute
Что делает: Позволяет указать индекс колонки CSV для свойства модели. Альтернатива Fluent API. Удобно для файлов без заголовков или когда порядок колонок фиксирован.
public class City
{
[Index(0)]
public string Name { get; set; }
[Index(1)]
public string Country { get; set; }
[Index(2)]
public int Population { get; set; }
}
using (var reader = new StreamReader("cities.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
// Файл без заголовков: "Moscow,Russia,12000000"
var cities = csv.GetRecords<City>().ToList();
}
4.9. CsvWriter.Flush()
Сигнатура: public void Flush()
Что делает: Принудительно сбрасывает буфер записи в поток. Важно вызывать перед закрытием writer'а, особенно при записи больших объёмов данных, чтобы гарантировать, что все данные записаны.
using (var writer = new StreamWriter("data.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(GetHugeData());
csv.Flush(); // гарантируем запись всех данных
}
4.10. CsvReader.TryGetField<T>()
Сигнатура: public bool TryGetField<T>(int index, out T field) (также перегрузка по имени колонки)
Что делает: Пытается прочитать поле и преобразовать его в указанный тип. Возвращает true при успехе, false — если поле пустое или не удалось преобразовать. Позволяет избежать исключений при невалидных данных.
using (var reader = new StreamReader("messy.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
if (csv.TryGetField<int>("Age", out int age))
Console.WriteLine($"Age: {age}");
else
Console.WriteLine("Age field is invalid or missing");
}
}
5. Реальный практический пример (100-200 строк кода)
Разработаем утилиту для обработки логов веб-сервера. CSV-файл содержит колонки: Timestamp, IP, Method, Url, StatusCode, ResponseTimeMs. Наша задача — прочитать файл, отфильтровать ошибки (4xx/5xx), сгруппировать по IP и записать отчёт в новый CSV.
using System.Globalization;
using CsvHelper;
using CsvHelper.Configuration;
// Модель для лога
public class LogEntry
{
public DateTime Timestamp { get; set; }
public string IP { get; set; }
public string Method { get; set; }
public string Url { get; set; }
public int StatusCode { get; set; }
public double ResponseTimeMs { get; set; }
}
// Модель для отчёта
public class ErrorReport
{
public string IP