Question

Manière typique de créer un CSV chaîne (pseudocode) :

  1. Créez un objet conteneur CSV (comme un StringBuilder en C#).
  2. Parcourez les chaînes que vous souhaitez ajouter en ajoutant une virgule après chacune.
  3. Après la boucle, supprimez cette dernière virgule superflue.

Exemple de code :

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

J'aime l'idée d'ajouter la virgule en vérifiant si le conteneur est vide, mais cela ne signifie-t-il pas plus de traitement car il faut vérifier la longueur de la chaîne à chaque occurrence ?

Je pense qu'il devrait y avoir un moyen plus simple/plus propre/plus efficace de supprimer cette dernière virgule.Des idées?

Était-ce utile?

La solution

Vous pourriez utiliser LINQ aux objets:

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

Évidemment, tout cela pourrait être fait sur une seule ligne, mais c'est un peu plus clair sur deux.

Autres conseils

Votre code n'est pas vraiment conforme à format CSV complet.Si vous générez simplement un CSV à partir de données sans virgules, espaces de début/fin, tabulations, nouvelles lignes ou guillemets, cela devrait aller.Cependant, dans la plupart des scénarios d’échange de données réels, vous avez besoin d’une mise en œuvre complète.

Pour générer un CSV approprié, vous pouvez utiliser ceci :

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

Ce n'est peut-être pas le code le plus efficace au monde, mais il a été testé.Le monde réel est nul comparé à un exemple de code rapide :)

Pourquoi ne pas utiliser l'une des bibliothèques CSV open source ?

Je sais que cela semble exagéré pour quelque chose qui semble si simple, mais comme vous pouvez le constater par les commentaires et les extraits de code, il y a plus que ce que l'on voit.En plus de gérer la conformité totale des CSV, vous souhaiterez éventuellement gérer à la fois la lecture et l'écriture des CSV...et vous souhaiterez peut-être une manipulation de fichiers.

j'ai utilisé Ouvrir le CSV sur l'un de mes projets auparavant (mais il y en a plein d'autres parmi lesquels choisir).Cela m’a certainement rendu la vie plus facile.;)

N'oubliez pas notre vieil ami "pour".Ce n'est pas aussi joli que foreach mais il a l'avantage de pouvoir démarrer au deuxième élément.

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

Vous pouvez également envelopper le deuxième Append dans un « if » qui teste si la propriété Name contient un guillemet double ou une virgule, et si c'est le cas, les échapper de manière appropriée.

Vous pouvez plutôt ajouter la virgule comme première chose dans votre foreach.

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

Vous pouvez également créer un tableau de c.Nom données et utilisation Chaîne.Join méthode pour créer votre ligne.

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

Ce n'est peut-être pas aussi performant que le Générateur de chaînes approche, mais cela a définitivement l’air plus propre.

En outre, vous voudrez peut-être envisager d'utiliser .CurrentCulture.TextInfo.ListSeparator au lieu d'une virgule codée en dur : si votre sortie doit être importée dans d'autres applications, vous pourriez rencontrer des problèmes.ListSeparator peut être différent selon les cultures, et MS Excel respecte à tout le moins ce paramètre.Donc:

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

J'aime l'idée d'ajouter la virgule en vérifiant si le conteneur est vide, mais cela ne signifie-t-il pas plus de traitement car il faut vérifier la longueur de la chaîne à chaque occurrence ?

Vous optimisez prématurément, la perte de performances serait négligeable.

Juste une pensée, mais n'oubliez pas de gérer les virgules et les guillemets ("") dans les valeurs des champs, sinon votre fichier CSV risque de perturber le lecteur des consommateurs.

J'ai écrit un petit cours pour cela au cas où quelqu'un d'autre le trouverait utile...

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

J'ai déjà utilisé cette méthode.La propriété Longueur de StringBuilder n'est PAS en lecture seule, donc la soustraire d'une manière ou d'une autre tronque le dernier caractère.Mais vous devez vous assurer que votre longueur n'est pas nulle au départ (ce qui se produirait si votre liste est vide), car définir une longueur inférieure à zéro est une erreur.

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

j'utilise CSVHelper - c'est une excellente bibliothèque open source qui vous permet de générer des flux CSV conformes un élément à la fois ou de personnaliser vos classes :

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 si vous mappez, quelque chose comme ceci : csvWriter.WriteRecords<ContactList>(contactList);

Que diriez-vous d'un peu de coupe ?

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

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

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

Que diriez-vous de savoir si vous êtes sur le premier élément et d'ajouter uniquement une virgule avant l'article si ce n'est pas le premier.

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();
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top