Pergunta

Maneira típica de criar um CSV string (pseudocódigo):

  1. Crie um objeto contêiner CSV (como um StringBuilder em C#).
  2. Percorra as strings que deseja adicionar, acrescentando uma vírgula após cada uma.
  3. Após o loop, remova a última vírgula supérflua.

Exemplo de código:

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();
}

Gosto da ideia de adicionar a vírgula verificando se o contêiner está vazio, mas isso não significa mais processamento, pois é necessário verificar o comprimento da string em cada ocorrência?

Eu sinto que deveria haver uma maneira mais fácil/mais limpa/eficiente de remover a última vírgula.Alguma ideia?

Foi útil?

Solução

Você poderia usar LINQ para objetos:

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

Obviamente, tudo isso poderia ser feito em uma linha, mas fica um pouco mais claro em duas.

Outras dicas

Seu código não é realmente compatível com formato CSV completo.Se você estiver apenas gerando CSV a partir de dados que não possuem vírgulas, espaços iniciais/finais, tabulações, novas linhas ou aspas, tudo bem.No entanto, na maioria dos cenários reais de troca de dados, você precisa da implementação completa.

Para gerar o CSV adequado, você pode usar isto:

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;
}

Pode não ser o código mais eficiente do mundo, mas foi testado.O mundo real é uma merda comparado ao código de amostra rápido :)

Por que não usar uma das bibliotecas CSV de código aberto que existem?

Eu sei que parece um exagero para algo que parece tão simples, mas como você pode perceber pelos comentários e trechos de código, há mais do que aparenta.Além de lidar com a conformidade total com CSV, você eventualmente desejará lidar com a leitura e a gravação de CSVs...e você pode querer manipulação de arquivos.

eu usei Abrir CSV em um dos meus projetos anteriores (mas há muitos outros para escolher).Certamente tornou minha vida mais fácil.;)

Não se esqueça do nosso velho amigo "para".Não é tão bonito quanto o foreach, mas tem a vantagem de poder começar no segundo elemento.

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();
}

Você também pode agrupar o segundo Append em um "if" que testa se a propriedade Name contém aspas duplas ou vírgula e, em caso afirmativo, escape-as adequadamente.

Em vez disso, você poderia adicionar a vírgula como a primeira coisa dentro do seu foreach.

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

Você também pode fazer uma série de c.Nome dados e uso String.Join método para criar sua linha.

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());
}

Isso pode não ter o mesmo desempenho que o StringBuilder abordagem, mas definitivamente parece mais limpo.

Além disso, você pode considerar usar .CurrentCulture.TextInfo.ListSeparator em vez de uma vírgula codificada - Se sua saída for importada para outros aplicativos, você poderá ter problemas com ela.ListSeparator pode ser diferente em diferentes culturas e, pelo menos, o MS Excel respeita essa configuração.Então:

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

Gosto da ideia de adicionar a vírgula verificando se o contêiner está vazio, mas isso não significa mais processamento, pois é necessário verificar o comprimento da string em cada ocorrência?

Você está otimizando prematuramente, o impacto no desempenho seria insignificante.

Só uma ideia, mas lembre-se de usar vírgulas e aspas (") nos valores dos campos, caso contrário, seu arquivo CSV pode prejudicar o leitor do consumidor.

Eu escrevi uma pequena aula para isso, caso alguém ache útil ...

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());
        }
    }
}

Eu usei esse método antes.A propriedade Length de StringBuilder NÃO é somente leitura, portanto, subtraí-la por um significa truncar o último caractere.Mas você precisa ter certeza de que seu comprimento não é zero para começar (o que aconteceria se sua lista estivesse vazia), porque definir o comprimento como menor que zero é um erro.

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();  
}

eu uso CSVHelper - é uma ótima biblioteca de código aberto que permite gerar fluxos CSV compatíveis, um elemento por vez, ou mapear suas classes de forma personalizada:

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();
}

ou se você mapear algo assim: csvWriter.WriteRecords<ContactList>(contactList);

Que tal alguns cortes?

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

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

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

Que tal rastrear se você está no primeiro item e adicionar apenas uma vírgula antes o item se não for o primeiro.

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();
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top