воскресенье, 14 сентября 2025 г.

25.03.08, Chad, CSVHelper, CS

25.03.08, Chad, CSVHelper, CS

CsvHelper

https://ask.chadgpt.ru/share/6156a741-5ea8-46bc-a593-f73c876631b8

CsvHelper is a powerful and flexible library for working with CSV (Comma Separated Values) files in .NET. It is developed by Josh Close and provides an easy way to read, write, and map data from CSV files into .NET objects, while ensuring high performance and configurability.

CsvHelper is highly regarded for its simplicity and versatility, allowing developers to handle complex CSV operations such as custom mappings, type conversions, and working with different cultures. It supports common use cases such as reading and writing CSV files, enforcing configurations like delimiter customizations, and automatically mapping properties.


Key Features of CsvHelper:

  1. Reading CSV Files:
    • Supports automatically mapping CSV data to class properties.
    • Handles headers for identifying columns.
    • Handles variable-length files with or without headers.
    • Provides options for reading from files, streams, or strings.
  2. Writing CSV Files:
    • Supports writing .NET object collections to CSV files.
    • Handles headers automatically if required.
    • Offers options to customize delimiter and data formatting.
  3. Auto-Class Mapping:
    • Uses reflection to map CSV columns directly to class properties.
    • Can override automatic mapping using custom mappings.
  4. Custom Conversions:
    • Supports custom type converters to handle special data types and formatting during reads and writes.
  5. Culture Support:
    • Handles different cultures (e.g., commas for decimal places, different date formats) via CultureInfo.
  6. Error Handling:
    • Provides robust error handling for incorrect data formats or missing information.
  7. High Performance:
    • Designed for speed, making it suitable for handling both small and very large CSV files efficiently.
  8. Built-In Configuration:
    • Offers a wide range of configuration options, including trimming whitespace, dealing with bad data, or configuring the delimiter (e.g., use of tabs, semicolons, etc.).

How to Install CsvHelper:

You can install CsvHelper using the NuGet Package Manager:

Install-Package CsvHelper

Or using the .NET CLI:

dotnet add package CsvHelper


Basic Examples:

Reading a CSV File

Suppose you have a CSV file named data.csv like this:

Id,Name,Age

1,John Doe,30

2,Jane Smith,25

3,Bob Johnson,40

You can read this CSV and map it to a C# class like so:

using CsvHelper;

using CsvHelper.Configuration;

using System.Globalization;

using System.IO;

using System.Linq;

 

public class Person

{

    public int Id { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

}

 

class Program

{

    static void Main()

    {

        using (var reader = new StreamReader("data.csv"))

        using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))

        {

            var records = csv.GetRecords<Person>().ToList();

            foreach (var record in records)

            {

                Console.WriteLine($"{record.Id}, {record.Name}, {record.Age}");

            }

        }

    }

}

Writing a CSV File

To write a collection of C# objects to a CSV file:

using CsvHelper;

using System.Globalization;

using System.IO;

 

public class Person

{

    public int Id { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

}

 

class Program

{

    static void Main()

    {

        var people = new List<Person>

        {

            new Person { Id = 1, Name = "John Doe", Age = 30 },

            new Person { Id = 2, Name = "Jane Smith", Age = 25 },

            new Person { Id = 3, Name = "Bob Johnson", Age = 40 }

        };

 

        using (var writer = new StreamWriter("output.csv"))

        using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))

        {

            csv.WriteRecords(people);

        }

    }

}

Custom Mapping

If the CSV file headers don't match your property names exactly, you can use custom mapping:

using CsvHelper;

using CsvHelper.Configuration;

using System.Globalization;

using System.IO;

using System.Linq;

 

public class Person

{

    public int Identifier { get; set; }

    public string FullName { get; set; }

    public int YearsOld { get; set; }

}

 

public sealed class PersonMap : ClassMap<Person>

{

    public PersonMap()

    {

        Map(m => m.Identifier).Name("Id");

        Map(m => m.FullName).Name("Name");

        Map(m => m.YearsOld).Name("Age");

    }

}

 

class Program

{

    static void Main()

    {

        using (var reader = new StreamReader("data.csv"))

        using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))

        {

            csv.Context.RegisterClassMap<PersonMap>();

            var records = csv.GetRecords<Person>().ToList();

            foreach (var record in records)

            {

                Console.WriteLine($"{record.Identifier}, {record.FullName}, {record.YearsOld}");

            }

        }

    }

}

Handling Custom Delimiters

If your CSV file uses a delimiter other than a comma (e.g., a semicolon), you can configure CsvHelper to use the correct delimiter:

var config = new CsvConfiguration(CultureInfo.InvariantCulture)

{

    Delimiter = ";",

};

 

using (var reader = new StreamReader("data.csv"))

using (var csv = new CsvReader(reader, config))

{

    var records = csv.GetRecords<Person>().ToList();

}


When to Use CsvHelper:

  • When dealing with large or complex CSV files.
  • When you need quick mappings between CSV data and strongly-typed objects.
  • When you need high performance and configurable options for CSV processing.

CsvHelper is a robust tool that removes much of the boilerplate work of handling CSV files, allowing developers to focus on the logic of their applications rather than worrying about parsing strings or managing edge cases. With its rich feature set and ease of use, it's a great fit for almost any .NET application that deals with CSV files.

Пока ничего не делай буду загружать файлы.
Скажу когда закончу
Необходимо из Четырех Кодов с Ошибками сделать один код без ошибок

Хорошо, ожидаю ваши файлы. Сообщите, как только загрузите их и будете готовы продолжить.

using CsvHelper;
using System;
using System.Collections.Generic;
using System.Formats.Asn1;
using System.Globalization;
using System.IO;
using System.Reflection;

public class Person
{
public int Id { get; set; }
public required string Name { get; set; }
public int Age { get; set; }
}

public class Program
{
public static List<T> CreateObjectsFromCsv<T>(string filename) where T : new()
{
var result = new List<T>();

using (var reader = new StreamReader(filename))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
csv.Configuration.HasHeaderRecord = true;
var records = csv.GetRecords<dynamic>().ToList(); //
Получаем данные из CSV

//
Используем рефлексию для установки свойств объектов
foreach (var record in records)
{
var obj = new T();
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

foreach (PropertyInfo property in properties)
{
if (record.ContainsKey(property.Name))
{
object value = Convert.ChangeType(record[property.Name], property.PropertyType);
property.SetValue(obj, value);
}
}

result.Add(obj);
}
}

return result;
}
public static void Main(string[] args)
{
var people = CreateObjectsFromCsv<Person>("example.csv");
foreach (var person in people)
{
Console.WriteLine(person.ToString());
}
}
}
-------------------------
using CsvHelper;
using System;
using System.Collections.Generic;
using System.Formats.Asn1;
using System.Globalization;
using System.IO;
using System.Reflection;

namespace Ca_Serial_02
{
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }

public override string ToString()
{
return $"Id: {Id}, Name: {Name}, Age: {Age}";
}
}

public class Program
{
public static List<T> CreateObjectsFromCsv<T>(string filename) where T : new()
{
var result = new List<T>();

using (var reader = new StreamReader(filename))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
csv.Configuration.HasHeaderRecord = true;
var records = csv.GetRecords<dynamic>().ToList(); //
Получаем данные из CSV

//
Используем рефлексию для установки свойств объектов
foreach (var record in records)
{
var obj = new T();
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

foreach (PropertyInfo property in properties)
{
if (record.ContainsKey(property.Name))
{
object value = Convert.ChangeType(record[property.Name], property.PropertyType);
property.SetValue(obj, value);
}
}

result.Add(obj);
}
}

return result;
}

public static void Main(string[] args)
{
var people = CreateObjectsFromCsv<Person>("example.csv");
foreach (var person in people)
{
Console.WriteLine(person.ToString());
}
}
}
}
-------------------------
using CsvHelper;
using CsvHelper.Configuration;
using System;
using System.Collections.Generic;
using System.Formats.Asn1;
using System.Globalization;
using System.IO;
using System.Reflection;

//
Класс Person остался прежним
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }

public override string ToString()
{
return $"Id: {Id}, Name: {Name}, Age: {Age}";
}
}

public class Program
{
public static List<T> CreateObjectsFromCsv<T>(string filename) where T : new()
{
var result = new List<T>();

using (var reader = new StreamReader(filename))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
//
Настраиваем CSV-парсер на использование заголовков
csv.Configuration.RegisterClassMap(new ClassMap<T>());
csv.Configuration.PrepareHeaderForMatch = (headers, index) => headers[index].ToLower();

var records = csv.GetRecords<T>().ToList(); //
Получаем данные из CSV

result = records;
}

return result;
}

public static void Main(string[] args)
{
var people = CreateObjectsFromCsv<Person>("example.csv");
foreach (var person in people)
{
Console.WriteLine(person.ToString());
}
}
}
-------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using CsvHelper;
using System.Globalization;
using System.IO;

//
Класс Person остался прежним
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }

public override string ToString()
{
return $"Id: {Id}, Name: {Name}, Age: {Age}";
}
}

internal class Program
{
public static List<T> CreateObjectsFromCsv<T>(string filename) where T : new()
{
var result = new List<T>();

using (var reader = new StreamReader(filename))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
//
Указываем, что первый ряд содержит заголовки
T t = csv.Context.RegisterClassMap<T>();

//
Читаем данные из CSV и создаем объекты
var records = csv.GetRecords<T>().ToList();

result = records;
}

return result;
}

public static void Main(string[] args)
{
var people = CreateObjectsFromCsv<Person>("example.csv");
foreach (var person in people)
{
Console.WriteLine(person.ToString());
}
}
}
Готово надо сделать Parser from csv

На основе предоставленных вами четырёх фрагментов кода я создам рабочий и исправленный пример программы, который будет корректно парсить данные из CSV-файла и создавать объекты с использованием CsvHelper.

Исправленный и объединённый код

using CsvHelper;

using CsvHelper.Configuration;

using System;

using System.Collections.Generic;

using System.Globalization;

using System.IO;

 

// Класс Person

public class Person

{

    public int Id { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

 

    public override string ToString()

    {

        return $"Id: {Id}, Name: {Name}, Age: {Age}";

    }

}

 

// Класс для обработки маппинга CSV-файла (опционально, если требуется ручная настройка столбцов)

public sealed class PersonMap : ClassMap<Person>

{

    public PersonMap()

    {

        // Соответствие столбцов CSV и свойств класса

        Map(m => m.Id).Name("Id");

        Map(m => m.Name).Name("Name");

        Map(m => m.Age).Name("Age");

    }

}

 

// Основной код

public class Program

{

    /// <summary>

    /// Общий метод для парсинга файлов CSV в список объектов переданного типа

    /// </summary>

    /// <typeparam name="T">Тип целевых объектов</typeparam>

    /// <param name="filename">Имя файла CSV</param>

    /// <returns>Список объектов типа T</returns>

    public static List<T> ParseCsvToObjects<T>(string filename) where T : class

    {

        // Результирующий список

        var result = new List<T>();

 

        // Чтение файла

        using (var reader = new StreamReader(filename))

        using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))

        {

            // Если требуется кастомное маппирование, оно регистрируется в этом месте

            csv.Context.RegisterClassMap<PersonMap>();

 

            // Читаем данные и преобразуем их в список объектов типа T

            result = new List<T>(csv.GetRecords<T>());

        }

 

        return result;

    }

 

    /// <summary>

    /// Главный метод программы

    /// </summary>

    /// <param name="args">Аргументы командной строки</param>

    public static void Main(string[] args)

    {

        try

        {

            // Укажите путь к вашему CSV-файлу

            var filename = "example.csv";

 

            // Парсим файл CSV и создаём объекты Person

            var people = ParseCsvToObjects<Person>(filename);

 

            // Вывод данных в консоль

            foreach (var person in people)

            {

                Console.WriteLine(person.ToString());

            }

        }

        catch (FileNotFoundException)

        {

            Console.WriteLine("CSV файл не найден. Проверьте указанный путь.");

        }

        catch (Exception ex)

        {

            Console.WriteLine($"Произошла ошибка при обработке CSV файла: {ex.Message}");

        }

    }

}


Объяснение кода:

  1. Person:
    • Представляет объект, поля которого будут заполняться данными из CSV.
    • Включает метод ToString() для вывода объекта в формате строки.
  2. PersonMap:
    • Отвечает за маппинг свойств класса (колонок в CSV) к полям CSV. Это полезно, если CSV-файл имеет заголовки, которые не совпадают с названиями свойств объекта, либо требуется особая обработка данных.
  3. ParseCsvToObjects<t>:
    • Универсальный метод (generic), способный работать с любыми типами объектов (например, Person, Product, и т.д.), а также преобразовывать данные из CSV.
    • Использует библиотеку CsvHelper для открытия CSV-файла, чтения строк и заполнения объектов.
  4. Main:
    • Обрабатывает ошибок, таких как отсутствие файла или некорректные данные.
    • Загружает данные из указанного CSV-файла, создаёт объекты Person и выводит их на консоль.

Пример CSV-файла

Вот пример содержимого example.csv, который подойдёт для работы с этим кодом:

Id,Name,Age

1,John Doe,30

2,Jane Smith,25

3,Emily Johnson,40


Пример вывода

Если CSV содержит указанные выше данные, программа выведет:

Id: 1, Name: John Doe, Age: 30

Id: 2, Name: Jane Smith, Age: 25

Id: 3, Name: Emily Johnson, Age: 40


Зависимости

Чтобы код заработал:

  1. Установите библиотеку CsvHelper:

dotnet add package CsvHelper

Или добавьте в файл csproj вручную.

  1. Убедитесь, что ваш CSV-файл корректно создан, и путь к нему указан правильно.

Этот код результативно сочетает аспекты каждого из предоставленных вами примеров и обеспечивает стабильность, универсальность и читаемость.

Этот метод через Reflection работает ?

Код, основанный на CsvHelper, который я предложил ранее, не использует Reflection для парсинга CSV. Вместо этого CsvHelper использует конфигурацию маппинга и встроенные обработчики для сопоставления данных CSV с объектами. Таким образом, Reflection, в чистом виде, в этом подходе не используется.

Однако, чтобы ответить на ваш вопрос в общем, да, если вы используете Reflection (отражение) для парсинга CSV в C#, это возможно, но имеет свои особенности.


Как рабочий пример через Reflection:

Вы можете написать собственный базовый парсер CSV, который будет обрабатывать данные и с использованием Reflection сопоставлять их с объектами. Например:

Код:

using System;

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.Reflection;

 

class Program

{

    static void Main(string[] args)

    {

        string csvPath = "data.csv"; // Укажите путь к вашему CSV-файлу

        var people = LoadCsv<Person>(csvPath);

 

        // Вывод результатов

        foreach (var person in people)

        {

            Console.WriteLine($"Id: {person.Id}, Name: {person.Name}, Age: {person.Age}");

        }

    }

 

    // Загрузка CSV-файла и преобразование строк в объекты типа T через Reflection

    static List<T> LoadCsv<T>(string filePath) where T : new()

    {

        var result = new List<T>();

        var lines = File.ReadAllLines(filePath);

 

        if (!lines.Any())

            throw new InvalidOperationException("CSV файл пустой!");

 

        var header = lines[0].Split(',');

 

        for (int i = 1; i < lines.Length; i++)

        {

            var obj = new T();

            var values = lines[i].Split(',');

 

            for (int j = 0; j < values.Length; j++)

            {

                var propertyName = header[j];

                var property = typeof(T).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);

 

                if (property != null && property.CanWrite)

                {

                    // Приведение строкового значения к типу свойства

                    var typedValue = Convert.ChangeType(values[j], property.PropertyType);

                    property.SetValue(obj, typedValue);

                }

            }

 

            result.Add(obj);

        }

 

        return result;

    }

}

 

// Пример целевого объекта

public class Person

{

    public int Id { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

}


Как это работает:

  1. Чтение CSV:
    Строки из файла
    data.csv читаются методом File.ReadAllLines, и первая строка используется как заголовок.
  2. Reflection:
    • Для каждой строки CSV создается объект типа T (в нашем случае Person).
    • Заголовки CSV сверяются с именами свойств класса (например, Id, Name, Age).
    • Через PropertyInfo и SetValue значения напрямую присваиваются свойствам объекта.
  3. Приведение типов:
    • Для каждого значения CSV используется Convert.ChangeType для приведения строки в правильный тип свойства.
  4. Результат:
    Объекты типа
    Person добавляются в список и возвращаются.

Входной файл (data.csv):

Id,Name,Age

1,John Doe,30

2,Jane Smith,25

3,Emily Johnson,40

Вывод:

Id: 1, Name: John Doe, Age: 30

Id: 2, Name: Jane Smith, Age: 25

Id: 3, Name: Emily Johnson, Age: 40


Преимущества метода через Reflection:

  1. Универсальность: Код может работать с любыми классами, если они соответствуют заголовкам CSV.
  2. Нет необходимости в сторонних библиотеках (например, CsvHelper).

Но есть минусы:

  1. Производительность: Reflection медленнее, чем прямое сопоставление (например, через сторонние библиотеки).
  2. Ошибки приведения типов: Если данные CSV не соответствуют ожидаемым типам, может быть выброшено исключение.
  3. Безопасность: Reflection требует правильного управления данными, поскольку ошибки могут затруднить отладку.
  4. Нет гибкости: Более сложные структуры CSV (например, вложенные объекты, преобразование форматов дат и т.п.) потребуют дополнительной работы.

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

Если вы собираетесь использовать парсинг в боевых проектах, лучше использовать библиотеку (CsvHelper), которая:

  • Работает быстрее.
  • Лучше обрабатывает ошибки.
  • Поддерживает больше форматов CSV и опций.

Используйте Reflection только при необходимости работы без внешних зависимостей или для задач низкой сложности.

Я просто понять хочу как может REfleсtion сопоставлять заголовки членам класса
Мне не очень понятно

Хорошо, давайте разберем это по шагам, чтобы было понятно, как Reflection может сопоставлять заголовки из CSV файле полям или свойствам класса!

Для начала смысл в том, что с помощью Reflection можно динамически получать названия полей или свойств класса, а затем связывать их с заголовками из CSV, если название заголовка совпадает с названием поля (или свойством).


Как это работает

  1. CSV-файл содержит заголовки: первая строка файла CSV, например:

Name,Age,Email

   John,30,john@example.com

   Jane,25,jane@example.com

Здесь заголовки Name, Age, и Email смогут быть связаны с полями/свойствами вашей модели, например:

public class Person

   {

       public string Name { get; set; }

       public int Age { get; set; }

       public string Email { get; set; }

   }

  1. Reflection узнает поля в классе: с помощью Reflection вы можете получить список всех свойств (или полей) класса Person вместе с их названиями. Например:

typeof(Person).GetProperties() // Возвращает массив PropertyInfo для свойств: Name, Age, Email

  1. Сопоставление названия заголовков с полями:
    • Вы берете заголовки из CSV (например, "Name", "Age", "Email").
    • Ищете соответствующее поле/свойство в классе Person, у которого имя совпадает с названием заголовка из CSV.
    • Используя Reflection, можно указать соответствующее поле для записи данных.
  2. Установка значений в поля через Reflection: как только вам известно, какое поле к какому заголовку подключено, вы используете Reflection, чтобы задать значение полям/свойствам для каждой строки CSV.

Пример

Объясним всё на примере кода. Вот небольшой пример, как это можно сделать:

using System;

using System.IO;

using System.Linq;

using System.Reflection;

using System.Collections.Generic;

 

public class Person

{

    public string Name { get; set; }

    public int Age { get; set; }

    public string Email { get; set; }

}

 

class Program

{

    static void Main(string[] args)

    {

        // Пример строк из CSV

        var csvLines = new List<string>

        {

            "Name,Age,Email",       // Заголовки (первая строка)

            "John,30,john@example.com",

            "Jane,25,jane@example.com"

        };

 

        // Извлекаем заголовки

        string[] headers = csvLines[0].Split(',');

 

        // Создадим список объектов Person

        var people = new List<Person>();

 

        // Перебираем строки (начиная со второй строки, т.е. данные CSV)

        for (int i = 1; i < csvLines.Count; i++)

        {

            // Разделяем данные строки CSV

            string[] values = csvLines[i].Split(',');

 

            // Создаем новый объект Person

            var person = new Person();

 

            // Получаем тип объекта Person

            var type = typeof(Person);

 

            // Проходим по каждому заголовку

            for (int j = 0; j < headers.Length; j++)

            {

                string header = headers[j];   // Заголовок (например, "Name")

                string value = values[j];    // Значение (например, "John")

 

                // Используем Reflection, чтобы найти свойство с таким именем

                PropertyInfo property = type.GetProperty(header);

 

                if (property != null)

                {

                    // Преобразуем строку из CSV в нужный для свойства тип

                    object convertedValue = Convert.ChangeType(value, property.PropertyType);

 

                    // Устанавливаем значение свойства через Reflection

                    property.SetValue(person, convertedValue);

                }

            }

 

            people.Add(person); // Добавляем объект Person в список

        }

 

        // Выводим результаты

        foreach (var person in people)

        {

            Console.WriteLine($"Name: {person.Name}, Age: {person.Age}, Email: {person.Email}");

        }

    }

}


Разбор работы кода

  1. Первая строка файла CSV (заголовки) разделяется запятой, чтобы получить названия столбцов (headers).
  2. Итеративно парсятся строки файла (начиная со второй строки), чтобы получить значения (values).
  3. Используется typeof(Person).GetProperty(header), чтобы найти свойство в классе Person, совпадающее с текущим заголовком header. Например:
    • Заголовок "Name" сопоставляется свойству Name.
    • Заголовок "Age" сопоставляется свойству Age.
    • Заголовок "Email" сопоставляется свойству Email.
  4. Функция Convert.ChangeType(value, property.PropertyType) конвертирует значение из строки в соответствующий тип данных (например, int, string, и т.д.).
  5. С помощью property.SetValue(person, convertedValue) значение записывается в объект person.

Что получится

В результате после выполнения кода будет создан список объектов Person:

Name: John, Age: 30, Email: john@example.com

Name: Jane, Age: 25, Email: jane@example.com


Пояснение ключевых моментов

  1. GetProperty — это метод Reflection для поиска свойства по имени. Если название заголовка совпадает с названием свойства класса, то оно вернёт объект PropertyInfo, с которым можно работать (например, задать новое значение).
  2. SetValue используется для установки значения в свойство конкретного объекта (в данном случае объекта person).
  3. Convert.ChangeType помогает корректно преобразовывать строковые значения из CSV в типы свойств, которые указаны в классе.

Преимущества подхода

  1. Динамика: Reflection автоматически сопоставляет заголовки с свойствами без необходимости жёстко задавать порядок или называть их вручную.
  2. Гибкость: Если структура класса и заголовков совпадает, можно легко использовать тот же код для разных классов и наборов данных.

Если это ещё где-то не до конца понятно, давайте разберём уточнения!

Гибкость: Если структура класса и заголовков совпадает, можно легко использовать тот же код для разных классов и наборов данных.
Весь смысл именно в этом - Чтобы использовать Парсер для разных классов.
Один раз написал и все.

Абсолютно верно! Если грамотно спроектировать парсер, можно добиться именно этого — универсальности и повторного использования. Давайте разберём, как правильно построить такой CSV-парсер, который сможет автоматически работать с любыми классами, где структура класса (свойства) соответствует заголовкам CSV.


Пример реализации универсального CSV-парсера на C#:

using System;

using System.Collections.Generic;

using System.Globalization;

using System.IO;

using System.Linq;

using System.Reflection;

 

public class CsvParser

{

    // Универсальный метод для преобразования строки CSV в коллекцию объектов заданного типа

    public static List<T> ParseCsv<T>(string csvFilePath, char delimiter = ',') where T : new()

    {

        // Читаем все строки файла CSV

        var lines = File.ReadAllLines(csvFilePath);

 

        if (lines.Length < 2)

            throw new Exception("CSV-файл пустой или содержит только заголовок!");

 

        // Первая строка файла — это заголовки (имена свойств)

        var headers = lines[0].Split(delimiter);

        var dataLines = lines.Skip(1); // Остальные строки — данные

 

        var result = new List<T>();

 

        // Перебираем строки данных

        foreach (var line in dataLines)

        {

            var values = line.Split(delimiter);

            var obj = new T();

 

            // Через Reflection сопоставляем данные со свойствами класса

            for (int i = 0; i < headers.Length; i++)

            {

                var propertyName = headers[i].Trim();

                var property = typeof(T).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);

 

                if (property == null)

                    continue; // Если свойства в классе нет, пропускаем этот столбец

 

                // Преобразуем строку значения из CSV в нужный тип

                var value = ConvertValue(values[i], property.PropertyType);

 

                property.SetValue(obj, value); // Устанавливаем значение для свойства

            }

 

            result.Add(obj);

        }

 

        return result;

    }

 

    // Метод для преобразования строки в нужный тип

    private static object ConvertValue(string value, Type targetType)

    {

        if (string.IsNullOrWhiteSpace(value))

        {

            if (targetType.IsValueType)

                return Activator.CreateInstance(targetType); // Для значимых типов задаём значение по умолчанию

            return null; // Для ссылочных типов возвращаем null

        }

 

        // Если это Nullable, нужно определить базовый тип (UnderlyingType)

        var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;

 

        // Преобразуем в целевой тип используя CultureInfo.InvariantCulture (для чисел и дат)

        return Convert.ChangeType(value, underlyingType, CultureInfo.InvariantCulture);

    }

}

 

// Пример класса с совпадающими свойствами

public class Person

{

    public string Name { get; set; }

    public int Age { get; set; }

    public float Height { get; set; }

    public bool IsMarried { get; set; }

}


Как это работает?

Универсальная логика обработки:

  1. Заголовок файла:
    Первая строка CSV используется для получения названий столбцов. Эти названия затем сопоставляются с именами свойств в классе (имена должны совпадать с точностью до регистра).
  2. Сопоставление с классом через Reflection:
    С помощью
    Type.GetProperty парсер автоматически ищет поле с названием из заголовка. Если поле найдено, для него устанавливается значение (через SetValue).
  3. Преобразование типов:
    Так как все данные из CSV представлены в виде строк, метод
    ConvertValue преобразует их в нужный тип. Это поддерживает:
    • Примитивные типы (int, float, double, bool);
    • Nullable-типы (например, int?);
    • Строки;
    • Даты, числа с использованием InvariantCulture.
  4. Гибкость:
    Код абсолютно универсален. Нужно только, чтобы названия столбцов в CSV-файле совпадали с названиями свойств в классе (с учётом регистра), например:

Name,Age,Height,IsMarried

   John,30,1.75,true

   Jane,25,1.60,false

Соответствует классу:

public class Person

   {

       public string Name { get; set; }

       public int Age { get; set; }

       public float Height { get; set; }

       public bool IsMarried { get; set; }

   }

Если структура совпадает, парсер автоматически создаёт экземпляры класса Person.

  1. Обработка ошибок:
    Если в CSV-файле есть столбцы, которым не соответствует свойство класса, они просто игнорируются. Это делает парсер более устойчивым.

Использование:

Теперь достаточно вызвать метод ParseCsv<T> с указанием типа объекта, чтобы получить объекты класса из любого подходящего CSV. Пример:

class Program

{

    static void Main(string[] args)

    {

        string filePath = "data.csv"; // Путь к вашему CSV-файлу

 

        // Парсим CSV в список объектов Person

        var people = CsvParser.ParseCsv<Person>(filePath);

 

        foreach (var person in people)

        {

            Console.WriteLine($"{person.Name} - {person.Age} лет, Рост: {person.Height}м, Женат: {person.IsMarried}");

        }

    }

}


Преимущества:

  • Модульность: Логику парсинга можно использовать для любых классов: просто замените тип T (например, на Product, Order, Car и т. д.).
  • Экономия времени: Один раз написали парсер — и он готов к работе с любой структурой данных.
  • Контроль качества данных: Код автоматически блокирует неподходящие или некорректные преобразования.
  • Простота расширения: Добавление новых классов не требует изменения логики парсера.

Если нужно добавить дополнительную функциональность (например, обработку сложных типов или умное сопоставление имени столбцов), реализацию можно расширить, оставаясь в рамках принципа универсальности.


Если будут вопросы по адаптации или доработке кода под ваши задачи, просто дайте знать!

Давайте этот Ваш последний код
Засунем в библиотеку с#.
public class CsvParser
{
// Универсальный метод для преобразования строки CSV в коллекцию объектов заданного типа
public static List<T> ParseCsv<T>(string csvFilePath, char delimiter = ',') where T : new()
{
// Читаем все строки файла CSV
var lines = File.ReadAllLines(csvFilePath);

if (lines.Length < 2)
throw new Exception("CSV-файл пустой или содержит только заголовок!");

// Первая строка файла — это заголовки (имена свойств)
var headers = lines[0].Split(delimiter);
var dataLines = lines.Skip(1); // Остальные строки — данные

var result = new List<T>();

// Перебираем строки данных
foreach (var line in dataLines)
{
var values = line.Split(delimiter);
var obj = new T();

// Через Reflection сопоставляем данные со свойствами класса
for (int i = 0; i < headers.Length; i++)
{
var propertyName = headers[i].Trim();
var property = typeof(T).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);

if (property == null)
continue; // Если свойства в классе нет, пропускаем этот столбец

// Преобразуем строку значения из CSV в нужный тип
var value = ConvertValue(values[i], property.PropertyType);

property.SetValue(obj, value); // Устанавливаем значение для свойства
}

result.Add(obj);
}

return result;
}

// Метод для преобразования строки в нужный тип
private static object ConvertValue(string value, Type targetType)
{
if (string.IsNullOrWhiteSpace(value))
{
if (targetType.IsValueType)
return Activator.CreateInstance(targetType); // Для значимых типов задаём значение по умолчанию
return null; // Для ссылочных типов возвращаем null
}

// Если это Nullable, нужно определить базовый тип (UnderlyingType)
var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;

// Преобразуем в целевой тип используя CultureInfo.InvariantCulture (для чисел и дат)
return Convert.ChangeType(value, underlyingType, CultureInfo.InvariantCulture);
}
}
Как сделать библиотеку я подзабыл. Напишите как засунуть код в библиотеку с#

Создание библиотеки в C# — это достаточно простая задача. Я опишу процесс пошагово, чтобы вы могли без труда разобраться.


Шаг 1: Создайте новый проект библиотеки класса (Class Library)

  1. Откройте Visual Studio (или любую другую IDE, например Rider или Visual Studio Code с поддержкой C#).
  2. Создайте новый проект:
    • Нажмите FileNewProject....
    • Введите в поле поиска "Class Library" и выберите шаблон Class Library (.NET).
    • Дайте подходящее имя проекту, например, CsvParserLibrary.
    • Выберите целевую платформу (обычно это .NET 6, .NET 7 и т. д., в зависимости от ваших требований).
    • Нажмите Create.
  3. Visual Studio создаст проект с файлом Class1.cs. Удалите этот файл, если он не нужен.

Шаг 2: Поместите ваш код в библиотеку

  1. В корневой папке проекта найдите файл Class1.cs (или замените его новым файлом).
  2. Замените содержимое нового файла CsvParser.cs вашим кодом. Вот окончательная версия:

using System;

using System.Collections.Generic;

using System.Globalization;

using System.IO;

using System.Linq;

using System.Reflection;

 

namespace CsvParserLibrary

{

    public class CsvParser

    {

        // Универсальный метод для преобразования строки CSV в коллекцию объектов заданного типа

        public static List<T> ParseCsv<T>(string csvFilePath, char delimiter = ',') where T : new()

        {

            // Читаем все строки файла CSV

            var lines = File.ReadAllLines(csvFilePath);

 

            if (lines.Length < 2)

                throw new Exception("CSV-файл пустой или содержит только заголовок!");

 

            // Первая строка файла — это заголовки (имена свойств)

            var headers = lines[0].Split(delimiter);

            var dataLines = lines.Skip(1); // Остальные строки — данные

 

            var result = new List<T>();

 

            // Перебираем строки данных

            foreach (var line in dataLines)

            {

                var values = line.Split(delimiter);

                var obj = new T();

 

                // Через Reflection сопоставляем данные со свойствами класса

                for (int i = 0; i < headers.Length; i++)

                {

                    var propertyName = headers[i].Trim();

                    var property = typeof(T).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);

 

                    if (property == null)

                        continue; // Если свойства в классе нет, пропускаем этот столбец

 

                    // Преобразуем строку значения из CSV в нужный тип

                    var value = ConvertValue(values[i], property.PropertyType);

 

                    property.SetValue(obj, value); // Устанавливаем значение для свойства

                }

 

                result.Add(obj);

            }

 

            return result;

        }

 

        // Метод для преобразования строки в нужный тип

        private static object ConvertValue(string value, Type targetType)

        {

            if (string.IsNullOrWhiteSpace(value))

            {

                if (targetType.IsValueType)

                    return Activator.CreateInstance(targetType); // Для значимых типов задаём значение по умолчанию

                return null; // Для ссылочных типов возвращаем null

            }

 

            // Если это Nullable, нужно определить базовый тип (UnderlyingType)

            var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;

 

            // Преобразуем в целевой тип, используя CultureInfo.InvariantCulture (для чисел и дат)

            return Convert.ChangeType(value, underlyingType, CultureInfo.InvariantCulture);

        }

    }

}


Шаг 3: Сборка проекта (Build)

  1. В Visual Studio:
    • Нажмите клавишу Ctrl + Shift + B, либо зайдите в меню BuildBuild Solution.
  2. После успешной сборки библиотека будет собрана в формате .dll и сохранена в папке bin вашего проекта:
    • Если ваша конфигурация — Debug, библиотека будет лежать по пути:
      <папка_проекта>/bin/Debug/net6.0/CsvParserLibrary.dll.
  3. Эта DLL теперь является вашей готовой библиотекой, которую можно использовать в других проектах.

Шаг 4: Использование библиотеки в других проектах

Теперь, когда библиотека собрана, её можно подключить к любому другому проекту. Вот как это сделать:

  1. Откройте проект, в котором вы хотите использовать библиотеку.
  2. Щёлкните правой кнопкой по проекту в обозревателе решений (Solution Explorer).
  3. Выберите AddProject Reference.
  4. Нажмите Browse, найдите выходной файл вашей сборки CsvParserLibrary.dll (он находится в папке binDebugnetX.0).
  5. Нажмите OK.

Теперь вы можете использовать библиотеку, добавив в свой код using CsvParserLibrary;.


Пример использования CsvParserLibrary:

Создайте CSV-файл products.csv с содержимым:

Id,Name,Price

1,ProductA,10.5

2,ProductB,20.0

3,ProductC,15.75

Определите класс для парсинга:

public class Product

{

    public int Id { get; set; }

    public string Name { get; set; }

    public decimal Price { get; set; }

}

И вот пример использования:

using CsvParserLibrary;

 

class Program

{

    static void Main(string[] args)

    {

        string csvFilePath = "products.csv";

       

        // Парсим CSV-файл в коллекцию объектов Product

        var products = CsvParser.ParseCsv<Product>(csvFilePath);

 

        // Выводим данные в консоль

        foreach (var product in products)

        {

            Console.WriteLine($"Id: {product.Id}, Name: {product.Name}, Price: {product.Price}");

        }

    }

}


Шаг 5: Распространение библиотеки

  1. Поделитесь DLL: Если вы хотите дать библиотеку клиентам или коллегам, вы можете просто отправить собранный .dll файл.
  2. Создание NuGet-пакета:
    • Если вы хотите распространять библиотеку через NuGet, выполните следующие шаги:
      • Убедитесь, что у проекта в файле .csproj заполнены метаданные (например, Authors, PackageId, Description и т. д.).
      • Соберите NuGet-пакет:

dotnet pack --configuration Release

- Загрузите созданный `.nupkg` файл на [NuGet.org](https://www.nuget.org/).

Теперь ваша библиотека готова к использованию!

С библитекой все работает.
Спасибо.
Теперь - последний вопрос.
Как быть если в class-е, который мы сериализуем, десериализуем присутсвуют поля с
enum. Например enum StatusCode {Fatal = 1, Warning = 2, Success = 3}
?

Если ваш класс содержит поле с типом enum (например, StatusCode), его сериализация и десериализация при использовании JSON-библиотек, таких как System.Text.Json или Newtonsoft.Json, не является сложной задачей. По умолчанию перечисления сериализуются как их числовые значения (например, 1, 2, 3 для Fatal, Warning, и Success), но вы можете настроить сериализацию, чтобы в JSON сохранялись строковые значения (например, "Fatal", "Warning", "Success"). Вот как это можно сделать:


1. Пример класса с enum

Предположим, у нас есть класс Product, содержащий поле StatusCode:

public class Product

{

    public int Id { get; set; }

    public string Name { get; set; }

    public decimal Price { get; set; }

    public StatusCode Status { get; set; }

}

 

public enum StatusCode

{

    Fatal = 1,

    Warning = 2,

    Success = 3

}


2. Сериализация и десериализация с использованием System.Text.Json

2.1. По умолчанию (сериализация как числа)

Если вы используете библиотеку System.Text.Json без дополнительной конфигурации, при сериализации Status сохранится числовое значение enum. Пример:

using System.Text.Json;

 

var product = new Product

{

    Id = 1,

    Name = "Sample Product",

    Price = 99.99m,

    Status = StatusCode.Warning

};

 

string json = JsonSerializer.Serialize(product);

Console.WriteLine(json); // {"Id":1,"Name":"Sample Product","Price":99.99,"Status":2}

 

var deserializedProduct = JsonSerializer.Deserialize<Product>(json);

Console.WriteLine(deserializedProduct.Status); // Warning

2.2. Сериализация в виде строк

Чтобы сериализовать и десериализовать перечисления как строки (например, "Warning" вместо 2), можно включить настройки сериализации по умолчанию:

var options = new JsonSerializerOptions

{

    WriteIndented = true, // Для читаемого JSON (по желанию)

    Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() }

};

 

string json = JsonSerializer.Serialize(product, options);

Console.WriteLine(json); // {"Id":1,"Name":"Sample Product","Price":99.99,"Status":"Warning"}

 

var deserializedProduct = JsonSerializer.Deserialize<Product>(json, options);

Console.WriteLine(deserializedProduct.Status); // Warning

Замечание: Конвертер JsonStringEnumConverter автоматически преобразует перечисления в строки и обратно.


3. Сериализация и десериализация с использованием Newtonsoft.Json

Библиотека Newtonsoft.Json также поддерживает настройку сериализации enum.

3.1. По умолчанию (сериализация как числа)

using Newtonsoft.Json;

 

string json = JsonConvert.SerializeObject(product);

Console.WriteLine(json); // {"Id":1,"Name":"Sample Product","Price":99.99,"Status":2}

 

var deserializedProduct = JsonConvert.DeserializeObject<Product>(json);

Console.WriteLine(deserializedProduct.Status); // Warning

3.2. Сериализация в виде строк

Чтобы сохранить строковые значения enum (например, "Warning" вместо 2), используйте атрибут [JsonConverter] или настройте настройки сериализации:

Способ 1: Глобальная настройка

var settings = new JsonSerializerSettings

{

    Formatting = Formatting.Indented, // Красиво форматирует JSON

    Converters = { new Newtonsoft.Json.Converters.StringEnumConverter() }

};

 

string json = JsonConvert.SerializeObject(product, settings);

Console.WriteLine(json); // {"Id":1,"Name":"Sample Product","Price":99.99,"Status":"Warning"}

 

var deserializedProduct = JsonConvert.DeserializeObject<Product>(json, settings);

Console.WriteLine(deserializedProduct.Status); // Warning

Способ 2: Локальная настройка через атрибут [JsonConverter]

В классе-перечислении можно указать атрибут [JsonConverter], чтобы использовать сериализацию поля как строку:

using Newtonsoft.Json;

using Newtonsoft.Json.Converters;

 

public enum StatusCode

{

    [EnumMember(Value = "Fatal")]

    Fatal = 1,

    [EnumMember(Value = "Warning")]

    Warning = 2,

    [EnumMember(Value = "Success")]

    Success = 3

}

 

public class Product

{

    public int Id { get; set; }

    public string Name { get; set; }

    public decimal Price { get; set; }

 

    [JsonConverter(typeof(StringEnumConverter))]

    public StatusCode Status { get; set; }

}

Теперь при сериализации:

string json = JsonConvert.SerializeObject(product);

Console.WriteLine(json); // {"Id":1,"Name":"Sample Product","Price":99.99,"Status":"Warning"}

 

var deserializedProduct = JsonConvert.DeserializeObject<Product>(json);

Console.WriteLine(deserializedProduct.Status); // Warning


4. Заключение

  • Если вы хотите работать с числами (например, 1, 2, 3), ничего менять не нужно, так как это поведение по умолчанию в большинстве JSON-библиотек.
  • Если предпочитаете видеть строковое представление enum (например, "Warning" вместо 2), используйте JsonStringEnumConverter с библиотекой System.Text.Json или StringEnumConverter с Newtonsoft.Json.
  • Вы также можете дополнительно проверить наличие некорректных значений при десериализации. Например, если JSON содержит неизвестный статус, это можно обработать, выбросив исключение или установив значение по умолчанию.

Впрочем, наверное можно при Сериализации просто интерпретировать enum int StatusCode - как int,
а в Десериализации уже конвертировать из int в enum StatusCode

Да, вы совершенно правы! Если вы хотите упростить процесс и работать с enum как с целым числом при сериализации в JSON и десериализации обратно, вы можете оставить преобразование в int во время сериализации, а уже во время десериализации конвертировать обратно из int в enum. Такой подход не требует использования дополнительных атрибутов или библиотек для преобразования, но подразумевает некоторую логику во время десериализации. Давайте разберем этот сценарий шаг за шагом.


Сериализация

При сериализации вы позволяете enum оставаться представленным как числовое значение. Это поведение, как правило, является стандартным для большинства библиотек JSON в C#. Например, если у вас есть такой enum:

public enum StatusCode

{

    Ok = 0,

    Warning = 1,

    Error = 2

}

И класс для сериализации:

public class Product

{

    public int Id { get; set; }

    public string Name { get; set; }

    public decimal Price { get; set; }

    public StatusCode Status { get; set; }

}

При вызове стандартной сериализации JSON (например JsonSerializer или Newtonsoft.Json), свойство Status будет записано как целое число:

var product = new Product { Id = 1, Name = "Sample Product", Price = 99.99m, Status = StatusCode.Warning };

 

string json = JsonSerializer.Serialize(product);

Console.WriteLine(json); // {"Id":1,"Name":"Sample Product","Price":99.99,"Status":1}

Здесь все, как вы хотите: Status сериализуется в виде целого числа.


Десериализация

Во время десериализации значение свойства Status, представленного как целое число, нужно привести обратно к вашему перечислению StatusCode. Это можно сделать с помощью стандартного преобразования через Enum или с помощью кастинга. Например:

string json = "{\"Id\":1,\"Name\":\"Sample Product\",\"Price\":99.99,\"Status\":1}";

 

var product = JsonSerializer.Deserialize<Product>(json);

Console.WriteLine(product.Status); // Output: 1 (enum as number)

Если тип Status изначально был определён как StatusCode, то C# автоматически приведёт его в enum, при условии, что значение допустимо. Однако если существует возможность, что в JSON может быть записано некорректное значение, нужно добавить дополнительную проверку.

Например, с использованием метода Enum.IsDefined, можно удостовериться, что значение int представляет успешное преобразование в enum:

string json = "{\"Id\":1,\"Name\":\"Sample Product\",\"Price\":99.99,\"Status\":1}";

var product = JsonSerializer.Deserialize<Product>(json);

 

if (!Enum.IsDefined(typeof(StatusCode), product.Status))

{

    throw new ArgumentException($"Invalid status code: {product.Status}");

}

 

Console.WriteLine(product.Status); // Output: Warning


Альтернативный подход: использование int вместо enum

Если вы хотите сделать процесс десериализации более управляемым, можно использовать int для хранения значения Status в модели (во время сериализации/десериализации), а преобразование в enum выполнять вручную. Например:

public class Product

{

    public int Id { get; set; }

    public string Name { get; set; }

    public decimal Price { get; set; }

    public int StatusCode { get; set; }

 

    public StatusCode Status

    {

        get => (StatusCode)StatusCode;

        set => StatusCode = (int)value;

    }

}

Теперь при работе с JSON свойство StatusCode будет сериализоваться/десериализоваться как int, но вы сможете обращаться к нему как к enum.

Пример:

var product = new Product

{

    Id = 1,

    Name = "Sample Product",

    Price = 99.99m,

    Status = StatusCode.Warning

};

 

// Сериализация: используется int представление

string json = JsonSerializer.Serialize(product);

Console.WriteLine(json); // {"Id":1,"Name":"Sample Product","Price":99.99,"StatusCode":1}

 

// Десериализация: восстанавливается int, преобразование в enum через свойство

var deserializedProduct = JsonSerializer.Deserialize<Product>(json);

Console.WriteLine(deserializedProduct.Status); // Output: Warning (enum)


Вывод

Если вы хотите сериализовать enum как число, а во время десериализации конвертировать его обратно в enum, существует несколько подходов:

  1. Использовать стандартное поведение сериализаторов, храня enum как int. Это самый простой и эффективный подход.
  2. Для более строгой обработки можно использовать свойство StatusCode как int в модели, а преобразование в enum организовать через отдельное поле/свойство.

Выбор подхода зависит от ваших задач, но в большинстве случаев, конвертация через int работает быстро и без лишних затрат на кастомизацию сериализации или библиотек.

GPT-4o

Комментариев нет:

Отправить комментарий