Вопрос

Типичный способ создания CSV- файл строка (псевдокод):

  1. Создайте объект-контейнер CSV (например, StringBuilder в C #).
  2. Пройдите по строкам, которые вы хотите добавить, добавляя запятую после каждой из них.
  3. После завершения цикла удалите последнюю лишнюю запятую.

Пример кода:

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();
    foreach (Contact c in contactList)
    {
        sb.Append(c.Name + ",");
    }

    sb.Remove(sb.Length - 1, 1);
    //sb.Replace(",", "", sb.Length - 1, 1)

    return sb.ToString();
}

Мне нравится идея добавить запятую, проверив, пуст ли контейнер, но разве это не означает дополнительной обработки, поскольку необходимо проверять длину строки при каждом вхождении?

Я чувствую, что должен быть более простой / чистый / эффективный способ удаления этой последней запятой.Есть какие-нибудь идеи?

Это было полезно?

Решение

Вы могли бы использовать ПРИВЯЗКА к объектам:

string [] strings = contactList.Select(c => c.Name).ToArray();
string csv = string.Join(",", strings);

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

Другие советы

Ваш код на самом деле не соответствует полный формат CSV.Если вы просто генерируете CSV из данных, в которых нет запятых, начальных / завершающих пробелов, табуляций, новых строк или кавычек, все должно быть в порядке.Однако в большинстве реальных сценариев обмена данными вам действительно требуется полная реализация.

Для генерации правильного CSV-файла вы можете использовать это:

public static String EncodeCsvLine(params String[] fields)
{
    StringBuilder line = new StringBuilder();

    for (int i = 0; i < fields.Length; i++)
    {
        if (i > 0)
        {
            line.Append(DelimiterChar);
        }

        String csvField = EncodeCsvField(fields[i]);
        line.Append(csvField);
    }

    return line.ToString();
}

static String EncodeCsvField(String field)
{
    StringBuilder sb = new StringBuilder();
    sb.Append(field);

    // Some fields with special characters must be embedded in double quotes
    bool embedInQuotes = false;

    // Embed in quotes to preserve leading/tralining whitespace
    if (sb.Length > 0 && 
        (sb[0] == ' ' || 
         sb[0] == '\t' ||
         sb[sb.Length-1] == ' ' || 
         sb[sb.Length-1] == '\t' ))
    {
        embedInQuotes = true;
    }

    for (int i = 0; i < sb.Length; i++)
    {
        // Embed in quotes to preserve: commas, line-breaks etc.
        if (sb[i] == DelimiterChar || 
            sb[i]=='\r' || 
            sb[i]=='\n' || 
            sb[i] == '"') 
        { 
            embedInQuotes = true;
            break;
        }
    }

    // If the field itself has quotes, they must each be represented 
    // by a pair of consecutive quotes.
    sb.Replace("\"", "\"\"");

    String rv = sb.ToString();

    if (embedInQuotes)
    {
        rv = "\"" + rv + "\"";
    }

    return rv;
}

Возможно, это не самый эффективный код в мире, но он был протестирован.Реальный мир отстой по сравнению с быстрым примером кода :)

Почему бы не использовать одну из существующих библиотек CSV с открытым исходным кодом?

Я знаю, это звучит как перебор для чего-то, что кажется таким простым, но, как вы можете судить по комментариям и фрагментам кода, здесь больше, чем кажется на первый взгляд.В дополнение к обработке полного соответствия CSV, вам в конечном итоге захочется обрабатывать как CSV для чтения, так и для записи...и вам может понадобиться манипулировать файлами.

Я использовал Открыть файл CSV в одном из моих предыдущих проектов (но есть много других на выбор).Это определенно облегчило мне жизнь.;)

Не забудь нашего старого друга "за".Это не так красиво выглядит, как foreach, но у него есть преимущество в том, что он может начинаться со второго элемента.

public string ReturnAsCSV(ContactList contactList)
{
    if (contactList == null || contactList.Count == 0)
        return string.Empty;

    StringBuilder sb = new StringBuilder(contactList[0].Name);

    for (int i = 1; i < contactList.Count; i++)
    {
        sb.Append(",");
        sb.Append(contactList[i].Name);
    }

    return sb.ToString();
}

Вы также могли бы обернуть второе добавление в "if", которое проверяет, содержит ли свойство Name двойные кавычки или запятую, и если да, то соответствующим образом экранировать их.

Вместо этого вы могли бы добавить запятую в качестве первого элемента внутри вашего foreach.

if (sb.Length > 0) sb.Append(",");

Вы также могли бы создать массив из c.Name данные и использование Строка.Соединение способ создания вашей строки.

public string ReturnAsCSV(ContactList contactList)
{
    List<String> tmpList = new List<string>();

    foreach (Contact c in contactList)
    {
        tmpList.Add(c.Name);
    }

    return String.Join(",", tmpList.ToArray());
}

Это может быть не так эффективно, как Строковый конструктор приближается, но это определенно выглядит чище.

Кроме того, возможно, вы захотите рассмотреть возможность использования .CurrentCulture.TextInfo.Разделитель списков вместо жестко заданной запятой - если ваши выходные данные будут импортированы в другие приложения, у вас могут возникнуть проблемы с этим.ListSeparator может отличаться в разных культурах, и MS Excel, по крайней мере, поддерживает этот параметр.Итак:

return String.Join(
    System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator,
    tmpList.ToArray());

Мне нравится идея добавить запятую, проверив, пуст ли контейнер, но разве это не означает дополнительной обработки, поскольку необходимо проверять длину строки при каждом вхождении?

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

Просто мысль, но не забудьте использовать запятые и кавычки (") в значениях полей, иначе ваш CSV-файл может сломаться для чтения потребителями.

Я написал небольшой класс для этого на случай, если кто-то другой сочтет это полезным...

public class clsCSVBuilder
{
    protected int _CurrentIndex = -1;
    protected List<string> _Headers = new List<string>();
    protected List<List<string>> _Records = new List<List<string>>();
    protected const string SEPERATOR = ",";

    public clsCSVBuilder() { }

    public void CreateRow()
    {
        _Records.Add(new List<string>());
        _CurrentIndex++;
    }

    protected string _EscapeString(string str)
    {
        return string.Format("\"{0}\"", str.Replace("\"", "\"\"")
                                            .Replace("\r\n", " ")
                                            .Replace("\n", " ")
                                            .Replace("\r", " "));
    }

    protected void _AddRawString(string item)
    {
        _Records[_CurrentIndex].Add(item);
    }

    public void AddHeader(string name)
    {
        _Headers.Add(_EscapeString(name));
    }

    public void AddRowItem(string item)
    {
        _AddRawString(_EscapeString(item));
    }

    public void AddRowItem(int item)
    {
        _AddRawString(item.ToString());
    }

    public void AddRowItem(double item)
    {
        _AddRawString(item.ToString());
    }

    public void AddRowItem(DateTime date)
    {
        AddRowItem(date.ToShortDateString());
    }

    public static string GenerateTempCSVPath()
    {
        return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString().ToLower().Replace("-", "") + ".csv");
    }

    protected string _GenerateCSV()
    {
        StringBuilder sb = new StringBuilder();

        if (_Headers.Count > 0)
        {
            sb.AppendLine(string.Join(SEPERATOR, _Headers.ToArray()));
        }

        foreach (List<string> row in _Records)
        {
            sb.AppendLine(string.Join(SEPERATOR, row.ToArray()));
        }

        return sb.ToString();
    }

    public void SaveAs(string path)
    {
        using (StreamWriter sw = new StreamWriter(path))
        {
            sw.Write(_GenerateCSV());
        }
    }
}

Я уже использовал этот метод раньше.Свойство Length StringBuilder НЕ доступно только для чтения, поэтому вычитание его на единицу означает усечение последнего символа.Но вы должны убедиться, что ваша длина не равна нулю для начала (что произошло бы, если бы ваш список был пуст), потому что установка длины меньше нуля является ошибкой.

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();

    foreach (Contact c in contactList)       
    { 
        sb.Append(c.Name + ",");       
    }

    if (sb.Length > 0)  
        sb.Length -= 1;

    return sb.ToString();  
}

Я использую CSVHelper ( помощь CSV ) - это отличная библиотека с открытым исходным кодом, которая позволяет вам генерировать совместимые потоки CSV по одному элементу за раз или настраивать отображение ваших классов:

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();
    using (StringWriter stringWriter = new StringWriter(sb))
    {
        using (var csvWriter = new CsvHelper.CsvWriter(stringWriter))
        {
            csvWriter.Configuration.HasHeaderRecord = false;
            foreach (Contact c in contactList)
            {
                csvWriter.WriteField(c.Name);
            }
        }
    }
    return sb.ToString();
}

или, если вы сопоставляете, то что-то вроде этого: csvWriter.WriteRecords<ContactList>(contactList);

Как насчет небольшой обрезки?

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();

    foreach (Contact c in contactList)
    {
        sb.Append(c.Name + ",");
    }

    return sb.ToString().Trim(',');
}

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

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();
    bool isFirst = true;

    foreach (Contact c in contactList) {
        if (!isFirst) { 
          // Only add comma before item if it is not the first item
          sb.Append(","); 
        } else {
          isFirst = false;
        }

        sb.Append(c.Name);
    }

    return sb.ToString();
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top