CsvHelper для C# (.NET): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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                    

Рекомендаций